构建后可见的Java非final int

时间:2011-03-14 13:56:47

标签: java concurrency

我有一个带有非final int变量的java类,我在构造函数中显式初始化为0.对该变量的所有其他访问都由ReentrantLock管理。我是否必须担心线程不会看到0的初始值,因为我没有在构造函数中使用锁?

2 个答案:

答案 0 :(得分:9)

是的,你必须担心。为避免出现问题,您需要安全发布对象引用。

来自Java Concurrency in Practice

  

要安全地发布对象,必须使对象的引用和对象的状态对其他对象可见   线程同时。正确构造的对象可以通过以下方式安全地发布:

     
      
  • 从静态初始化程序初始化对象引用;
  •   
  • 将对它的引用存储到易失性字段或AtomicReference中;
  •   
  • 将对它的引用存储到正确构造的对象的最终字段中;或
  •   
  • 将对它的引用存储到由锁定正确保护的字段中。
  •   

在其他情况下,您可以(理论上)面对在构造函数调用完成之前new的结果可用于其他线程的情况(由于可能的操作重新排序)。

但请注意,如果0是默认值而不是构造函数中写入的值,则保证其可见(JLS §17.4.4):

  
      
  • 对每个变量写入默认值(零,false或null)同步 -   在每个线程中的第一个动作。虽然看起来有点儿   奇怪的是在包含该对象的对象之前向变量写入默认值   变量被分配,概念上每个对象都是在开始时创建的   程序及其默认初始化值
  •   

答案 1 :(得分:2)

来自Java Concurrency in Practice:

  

不可变的对象必须是   安全发布,通常   需要同时进行同步   发布和消费线程。

仅通过不在构造函数中发布其引用来安全地发布对象。即构造函数不会强制执行必要的before-before关系。因此,即使您没有在其构造函数中发布对象引用,您仍然可能会遇到并发问题。有关详细信息和示例,请参阅本书中的relevant chapter

为了做出安全的出版物,作者提出了以下方法:

  

安全地发布对象,两者都是   引用对象和   必须使对象的状态可见   其他线程同时。一个   正确构造的对象可以   安全发布者:

     

初始化对象引用   静态初始化器;

     

将对它的引用存储到   volatile字段或AtomicReference;

     

将参考文献存入决赛   正确建造的领域   宾语;或

     

将对它的引用存储到字段中   这是由一把锁妥善保护的。   本质上,必须在对象的构造和另一个线程访问该对象之间引入适当的“发生在之前”关系。

正如作者所指出的那样,通过线程安全集合传递的对象也是安全发布的(例如,通过LinkedBlockingQueue等通过工作线程传递的项目)。

将值存储到原始int字段(但不是像long这样的64位字段)确实是原子的,这意味着即使您访问该字段也无法观察到“奇怪”值从不同的线程以非线程安全的方式。但是当一个物体尚未正确构建时,可能会发生其他不良事件(说实话,我不知道究竟会发生什么,但肯定不值得一试)。

总而言之,您需要安全地发布对象,此时将值正确设置为0并正确实例化对象。