我有一段看起来像这样的代码:
Snippet A:
class Creature {
private static long numCreated;
public Creature() {
synchronized (Creature.class) {
numCreated++;
}
}
public static long numCreated() {
return numCreated;
}
}
根据我的理解,由于numCreated
的读取未同步,如果Thread-A在下午1点创建Creature
,而Thread-B在下午2点创建numCreated()
,{{1}可能已经返回0或1(即使Thread-A在晚上1点05分完成对象的初始化)。
所以我将numCreated()
添加到synchronized
:
Snippet B :
numCreated()
并且一切都很好,除了我在想,如果我将其修改为 Snippet C ,变量class Creature {
private static long numCreated;
public Creature() {
synchronized (Creature.class) {
numCreated++;
}
}
public static synchronized long numCreated() { // add "synchronized"
return numCreated;
}
}
是否仍然正确同步?
Snippet C:
numCreated
使用 Snippet C ,可以保证一旦Thread-A在下午1:05完成对象创建,Thread-B对class Creature {
private static volatile long numCreated; // add "volatile"
public Creature() {
synchronized (Creature.class) {
numCreated++;
}
}
public static long numCreated() { // remove "synchronized"
return numCreated;
}
}
的调用肯定会返回{{1} }?
PS:据我所知,在实际情况下,我可能会使用numCreated()
,但这是出于学习目的
答案 0 :(得分:6)
在每次后续读取之前发生对易失性字段的写入 那个领域。易失性字段的写入和读取具有相似之处 进入和退出监视器时的内存一致性效果,但确实如此 不需要互斥锁定。
所以答案是肯定的。构造函数中volatile的写入发生在numCreated()
中读取volatile之前。并且由于非原子增量仍然在同步块中完成,因此同步是正常的(增量不是原子的,但写入volatile的长度是)。