我对同步实例方法和静态方法感到困惑。 我想编写一个线程安全类,如下所示:
public class safe {
private final static ConcurrentLinkedQueue<Object> objectList=
new ConcurrentLinkedQueue<Object>();
/**
* retrieves the head of the object and prints it
*/
public synchronized static void getHeadObject() {
System.out.println(objectList.peek().toString());
}
/**
* creates a new object and stores in the list.
*/
public synchronized void addObject() {
Object obj=new Object();
objectList.add(obj);
}
}
在静态方法上进行同步会锁定safe.class锁,并且在实例方法上进行同步将锁定此问题,因此将达到不一致的状态。
如果我想为下面的代码片段实现一致的状态,那该怎么办呢?
答案 0 :(得分:2)
首先,ConcurrentLinkedQueue不需要显式同步。见this answer。
其次,您始终可以同步您正在访问的对象:
public class safe {
private final static ConcurrentLinkedQueue<Object> objectList=
new ConcurrentLinkedQueue<Object>();
/**
* retrieves the head of the object and prints it
*/
public static void getHeadObject() {
synchronized(objectList){
System.out.println(objectList.peek().toString());
}
}
/**
* creates a new object and stores in the list.
*/
public void addObject() {
Object obj=new Object();
synchronized(objectList){
objectList.add(obj);
}
}
}
答案 1 :(得分:1)
编辑:我假设你的意思是Queue<Object> objectList
而不是ConcurrentLinkedQueue<Object> objectList
。 ConcurrentLinkedQueue<Object>
已经为您完成了所有线程安全,这意味着您可以随心所欲地调用objectList.peek()
而无需担心竞争条件。如果您正在开发多线程程序,但这对于了解线程安全性而言并不是那么好。
您的方法不必是synchronized
,假设您一次有一个线程在一个对象实例上运行,但是如果您需要有多个类的实例都引用相同的静态类变量,你需要synchronized
这样的类变量:
public static void getHeadObject() {
synchronized(safe.objectList) {
System.out.println(objectList.peek().toString());
}
}
这会锁定objectList
,并且只要程序在同步块内,就不允许在任何其他线程中读取或写入它。对所有其他方法执行相同操作synchronized
。
注意:强>
但是,由于你只进行了一次简单的get操作List.peek()
,所以你真的不需要在objectList
上进行同步,因为在竞争条件下,它将获得一个值List
或其他。竞争条件的问题是当执行多个复杂的读/写操作时,它们之间的值会发生变化。
例如,如果您的班级PairInt
包含PairInt.x
和PairInt.y
字段,其约束条件为x = 2y
,并且您想要
System.out.println(myIntPair.x.toString() + ", " + myIntPair.y.toString());
另一个主题是同时更新x
和y
的值,
myIntPair.y = y + 3;
myIntPair.x = 2 * y;
写线程在您的读线程myIntPair
和myIntPair.x.toString()
之间修改了myIntPair.y.toString()
,您可能会得到一个看起来像(10, 8)
的输出,这意味着如果您正在操作假设x == 2 * y
可能导致程序崩溃。
在这种情况下,您的阅读需要使用synchronized
,但对于更简单的内容,例如peek()
上正在添加或删除的简单object
,请勿在队列,synchronized
可以,在大多数情况下被删除。实际上,对于string
,int
,bool
等,应删除简单读取的synchronized
条件。
但是,对于非显式线程安全的操作(即已由java处理),写入应始终为synchronized
。只要您获得多个资源,或者要求您的资源在整个操作过程中保持不变,就像对其执行多行逻辑那样,那么必须使用 synchronized
答案 2 :(得分:0)
一些评论:
Safe
,而不是safe
)static
出现在方法声明synchronized
之前
static
位于字段声明中的final
之前ConcurrentLinkedQueue
已经是线程安全的,所以在你给出的例子中不需要同步。public static synchronized getHeadObject(){} //monitor = Safe.class
public static synchronized addObject(){} //monitor = this
因此,要回答您的具体问题,您可以使用单独的静态对象作为锁:
public class Safe {
private static final ConcurrentLinkedQueue<Object> objectList =
new ConcurrentLinkedQueue<Object>();
// lock must be used to synchronize all the operations on objectList
private static final Object lock = new Object();
/**
* retrieves the head of the object and prints it
*/
public static void getHeadObject() {
synchronized (lock) {
System.out.println(objectList.peek().toString());
}
}
/**
* creates a new object and stores in the list.
*/
public void addObject() {
synchronized (lock) {
Object obj = new Object();
objectList.add(obj);
}
}
}