有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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的状态无关。这样,另一个线程也可以看到字段的默认值

问题:我们如何保证在构造函数或实例初始值设定项中分配的字段值的内存一致性

我认为我们必须声明它们volatilefinal,因为分配引用和构造函数调用之间没有happens-before关系。但是很多库类并不是这样声明字段的java.lang.String就是一个例子:

public final class String 
          implements java.io.Serializable, Comparable<String>, CharSequence{
    //...
    private int hash; //neither final nor volatile
    //...
}

共 (1) 个答案

  1. # 1 楼答案

    final可以保证在实例构造之后,您将看到分配给实例变量的值,而无需执行任何进一步的操作。您只需要确保不泄漏构造函数中构造的实例

    volatile还可以保证看到为某个实例变量设置的默认值,因为实例变量初始值设定项保证在每个JLS 12.5 Creation of New Class Instances的构造函数结束之前执行

    安全出版并不是一件小事,但如果你坚持使用其中一种流行的机制来实现它,你应该会很好。你可以看一下Safe Publication and Safe Initialization in Java了解一些更有趣的细节

    至于String.hash,这是所谓的良性数据竞争的一个流行例子。对hash实例变量的访问既允许读写竞争,也允许写写两次竞争。为了说明后者,两个线程可以同时:

    • 请参见0的初始值
    • 确定他们是第一个计算哈希的人
    • 计算散列码并在不进行任何同步的情况下写入同一变量

    由于两个原因,这场比赛仍然被允许并被认为是良性的:

    1. 不可变String实例的哈希代码计算是一个幂等运算
    2. 32位值的写入保证不会被撕裂

    即使是良性的数据竞争也不被推荐。见Benign data races: what could possibly go wrong?Nondeterminism is unavoidable, but data races are pure evil