我在“Java Concurrency In Practice”中读到“在完全构造之前发布对象会损害线程安全性”。 有人可以解释一下吗?
答案 0 :(得分:3)
考虑以下代码:
public class World{
public static Point _point;
public static void main(String[] args){
new PointMaker().start();
System.out.println(_point);
}
}
public class Point{
private final int _x, _y;
public Point(int x, int y){
_x = x;
World._point = this;//BAD: publish myself before I'm fully constructed
//some long computation here
_y = y;
}
public void toString(){
return _x + "," + _y;
}
}
public class PointMaker extends Thread{
public void run(){
new Point(1, 1);
}
}
由于Point
在设置_y
的值之前发布了自己,因此对println
的调用可能会产生"1,0"
而不是预期的"1,1"
。
(请注意,如果"null"
+ PointMaker
在调用{{1}之前未能设置Point.<init>
字段,则可能会产生World._point
执行。)
答案 1 :(得分:1)
允许new运算符在类的构造函数完成之前返回一个值。因此,变量可能不会读取null但包含未初始化的类实例。这是由于字节重新排序而发生的。
一些澄清: 从单线程的角度来看,允许JVM重新排序某些指令。在传统上创建实例时,您会认为它是这样的:
实际上JVM可能会执行以下操作:
这具有性能优势,因为地址不需要再次查找。从单线程的角度来看,这不会改变逻辑的顺序。你的程序运行正常。但这在多线程代码中造成了问题。这意味着可以在构造函数运行之前发布引用。因此,您需要“先发生”规则以确保实例已完全初始化。声明变量volatile dos强制执行此类发生之前的规则。
有关重新排序的更多信息: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#reordering