我有一个带有非final int变量的java类,我在构造函数中显式初始化为0.对该变量的所有其他访问都由ReentrantLock管理。我是否必须担心线程不会看到0的初始值,因为我没有在构造函数中使用锁?
答案 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并正确实例化对象。