在http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html的大约底部,它说:
双重锁定不可变对象
如果Helper是一个不可变对象,Helper的所有字段都是final,那么双重检查锁定将无需使用volatile字段即可。我们的想法是对不可变对象(如String或Integer)的引用应该与int或float的行为方式大致相同;读取和写入对不可变对象的引用是原子的。
可变的样本和解释如下:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}
它没有工作的第一个原因
最明显的原因是它没有起作用,初始化Helper对象和写入辅助对象字段的写入可以无序完成或感知。因此,调用getHelper()的线程可以看到对辅助对象的非空引用,但是请参阅辅助对象的字段的默认值,而不是构造函数中设置的值。
如果编译器内联对构造函数的调用,那么如果编译器可以证明构造函数不能抛出异常或执行同步,则可以自由地重新排序初始化对象和写入辅助对象字段的写入。
即使编译器没有重新排序这些写入,在多处理器上,处理器或内存系统也可能重新排序这些写入,正如在另一个处理器上运行的线程所感知的那样。
我的问题是:为什么不可变课程没有问题?我看不出重新排序与该类是否可变的任何关系。
由于
答案 0 :(得分:1)
为常规对象“破解”代码的原因是helper
可能为非null但指向尚未完全初始化的对象,如引用中所述
但是,如果Helper类是不可变的,意味着它的所有字段都是final,the Java Memory Model保证它们被安全发布,即使该对象通过数据竞争可用(在您的示例中就是这种情况) :
final
字段还允许程序员在没有同步的情况下实现线程安全的不可变对象。 线程安全的不可变对象被所有线程看作不可变,即使使用数据争用传递线程之间的不可变对象的引用 。这可以提供安全保证,防止错误或恶意代码滥用不可变类。必须正确使用final
字段才能保证不变性。
答案 1 :(得分:0)
不可变类确实存在问题。在JSR133中对Java内存进行更改之后,您引用的部分为为真。
具体而言,影响不可变对象的更改与对final
关键字所做的某些更改有关。结帐http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalRight。
重要的部分是:
对象的最终字段的值在其构造函数中设置。假设对象是“正确”构造的,一旦构造了一个对象,分配给构造函数中最终字段的值对于所有其他线程都是可见的,而不会同步。