我对同步实例方法和静态方法感到困惑。我想编写一个线程安全类,如下所示:
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);
}
}
在静态方法上同步将锁定安全.class锁定,在实例方法上同步将锁定在此方法上,因此将达到不一致的状态。
如果我想为下面的代码片段实现一致的状态,如何实现?
首先,ConcurrentLinkedQueue 不需要显式同步。看到这个答案。
其次,您始终可以同步正在访问的对象:
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);
}
}
}
编辑:我假设你的意思是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
的所有其他方法执行相同的操作。
注意:
但是,由于您只执行一个简单的获取操作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
,但对于更简单的事情,例如在队列中添加或删除而不是修改的简单object
peek()
,在大多数情况下可以删除synchronized
。 事实上,对于string
、int
、bool
等,应该删除简单读取的synchronized
条件。
但是,应始终对未明确线程安全的操作(即已由 java 处理的操作)synchronized
写入。一旦您获得了多个资源,或者要求您的资源在整个操作过程中保持不变,因为您对其进行了多行逻辑,那么您必须使用synchronized
几点评论:
- Java约定:
- 类
- 名应该在驼峰大小写中(即称你的类
Safe
,而不是safe
) -
static
在方法声明中synchronized
之前 -
static
在字段中声明final
之前
- 名应该在驼峰大小写中(即称你的类
- 正如其他人已经说过的,
ConcurrentLinkedQueue
已经是线程安全的,因此在您给出的示例中不需要同步。 - 混合静态和非静态方法的方式看起来很奇怪。
- 假设您的实际用例更复杂,并且您需要一种方法来运行原子操作,那么正如您所指出的,您的代码不起作用,因为 2 个同步方法不会在同一监视器上同步:
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);
}
}
}