java - 使用 volatile 字段安全发布对象

标签 java concurrency volatile java-memory-model safe-publication

来自 Java Concurrency In Practice 一书:

To publish an object safely, both the reference to the object and the object’s state must be made visible to other threads at the same time. A properly constructed object can be safely published by:

  • Initializing an object reference from a static initializer;
  • Storing a reference to it into a volatile field or AtomicReference;
  • Storing a reference to it into a final field of a properly constructed object; or
  • Storing a reference to it into a field that is properly guarded by a lock.

我的问题是:
为什么要点 3 具有约束:“ 的正确构造的对象 ”,而要点 2 没有?
以下代码是否安全地发布 map实例?我认为代码符合要点 2 的条件。
public class SafePublish {

    volatile DummyMap map = new DummyMap();

    SafePublish() throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // Safe to use 'map'?
                System.out.println(SafePublish.this.map);
            }
        }).start();
        Thread.sleep(5000);
    }

    public static void main(String[] args) throws InterruptedException {
        SafePublish safePublishInstance = new SafePublish();
    }
 

public class DummyMap {
    DummyMap() {
        System.out.println("DummyClass constructing");
      }
  }
} 
以下调试快照图片显示了 map实例是 null执行时正在进入SafePublish的施工中.如果另一个线程现在试图读取 map 会发生什么引用?阅读安全吗?
enter image description here

最佳答案

因为final字段保证对其他线程可见 只有在对象构建之后 而写入 volatile 的可见性字段保证没有任何附加条件。
来自 jls-17 , 在 final领域:

An object is considered to be completely initialized when its constructor finishes. 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.


volatile领域:

A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).


现在,关于您的具体代码示例,JLS 12.5保证在执行构造函数中的代码之前进行字段初始化(请参阅 JLS 12.5 中的步骤 4 和 5,此处引用有点太长)。因此,程序顺序保证构造函数的代码会看到map初始化,不管它是不是volatilefinal或者只是一个普通的领域。由于在字段写入和线程开始之前存在 Happens-Before 关系,即使您在构造函数中创建的线程也会看到 map如初始化。
请注意,我特别写了“在执行构造函数中的代码之前”而不是“在执行构造函数之前”,因为这不是 JSL 12.5 做出的保证(阅读它!)。这就是为什么您在构造函数代码的第一行之前在调试器中看到 null 的原因,但保证构造函数中的代码看到该字段已初始化。

关于java - 使用 volatile 字段安全发布对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67956977/

相关文章:

java - 复杂枚举的联合测试

java - 将控制权返回给java中的switch语句?

java - JTable单元格更新问题

java - 你如何获得 "ignore"java.util.concurrent.Future 对象?

c++ - C++ 中的可变类

c++ - 如何在cpp中定义volatile元素的map

Java 求两个整数的中点

java - 有哪些好的资源可以解释非阻塞 IO 在幕后如何工作?

c++ - 并发/多线程:是否可以通过这种方式生成相同的输出?

Java:调用 super.clone() 方法, volatile int 值未复制到克隆对象中