java是保证在线程中可见的字段默认值吗?
在讨论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);
}
}
此类看起来是线程安全的,因为对其字段的访问由同步方法管理。这样,我们所要做的就是安全地发布它。例如:
public final StateHolder stateHolder = new StateHolder();
它能被认为是安全的出版物吗?我想不行。查阅final field semantic(我的重点)我发现唯一需要保证的是stateHolder
引用不是过时的引用
A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.
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
//...
}
# 1 楼答案
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