讨论this answer我想知道为什么在分配默认值时我们没有使用同步。
class StateHolder {
private int counter = 100;
private boolean isActive = false;
public synchronized void resetCounter() {
counter = 0;
isActive = true;
}
public synchronized void printStateWithLock() {
System.out.println("Counter : " + counter);
System.out.println("IsActive : " + isActive);
}
public void printStateWithNoLock() {
System.out.println("Counter : " + counter);
System.out.println("IsActive : " + isActive);
}
}
此类看起来是线程安全的,因为对其字段的访问由synchronized方法管理。这样,我们所要做的就是安全地发布它。例如:
public final StateHolder stateHolder = new StateHolder();
它可以被视为安全出版物吗?我想不,它不可以。咨询final field semantic(强调我的)我发现唯一保证的是stateHolder
引用不是陈旧的。 :
只能在该对象后看到对象引用的线程 已经完全初始化,保证看到正确 对象的最终字段 的初始化值。
final
字段语义不关心final
字段引用的ojbect的状态。这样,另一个线程也可以看到字段的默认值。
问题: 我们如何保证在构造函数或实例初始化程序中分配的字段值的内存一致性?
我认为我们必须声明它们volatile
或final
,因为在分配引用和构造函数调用之间没有happens-before关系。但是很多库类都不会以这种方式声明字段。 java.lang.String就是一个例子:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence{
//...
private int hash; //neither final nor volatile
//...
}
答案 0 :(得分:2)
final
可以保证在实例构建后您将看到实例变量的赋值,而无需任何进一步的操作。您只需要确保不要在构造函数中泄漏构造的实例。
volatile
还可以保证您将看到为某个实例变量设置的默认值,因为实例变量初始值设定项保证在每个JLS 12.5 Creation of New Class Instances的构造函数结束之前执行
安全发布并非完全无关紧要,但如果您坚持使用其中一种流行的机制来实现它,那么您应该完全没问题。您可以查看Safe Publication and Safe Initialization in Java了解更多有趣的细节。
至于String.hash
,它是所谓的良性数据竞赛的一个流行的例子。对hash
实例变量的访问允许读写的比赛和两次写入的比赛。为了说明后者,两个线程可以同时:
由于两个原因,比赛仍被允许并被认为是良性的:
String
实例的散列码计算是幂等操作。尽管如此,仍然不建议进行良性数据竞赛。请参阅Benign data races: what could possibly go wrong?或Nondeterminism is unavoidable, but data races are pure evil。