java.lang.String只有效地是不变的。"Java并发性实践“的Brian说,只有在安全发布的情况下,有效不可变的对象才是线程安全的。现在,假设我不安全地发布这样的字符串:
public class MultiThreadingClass {
private String myPath ="c:\\somepath";
//beginmt runs simultaneously on a single instance of MultiThreading class
public void beginmt(){
Holder h = new Holder();
h.setPath(new File(myPath)); //line 6
h.begin();
}
}
public class Holder {
private File path;
public void setPath(File path){
this.path = path;
}
public void begin(){
System.out.println(path.getCanonicalPath()+"some string");
}
}在MultiThreadingClass使用其构造函数初始化时,第6行的文件构造函数可能看不到myPath的值。
然后,在构造不安全发布的字符串对象大约3秒钟后,MultiThreadingClass上的线程仍在运行。文件构造函数可能仍然看不到myPath的值吗?
发布于 2014-08-22 03:47:58
你的陈述,你问你的问题是:
在MultiThreadingClass使用其构造函数初始化时,第6行的文件构造函数可能看不到myPath的值。
答案很复杂。您不需要担心String对象中的char数组String。正如我在注释中提到的,因为它是在构造函数中分配的final字段,而且由于String在分配final字段之前不传递引用给自己,所以它总是安全发布的。您也不需要担心hash和hash32字段。它们不是安全发布的,但只能具有值0或有效的哈希代码。如果它们仍然为0,则方法String.hashCode将重新计算值--它只会导致其他线程重新计算hashCode,而这已经在另一个线程中早些时候完成了。
MultiThreadingClass中的引用MultiThreadingClass不能安全地发布,因为它不是final。“此时MultiThreadingClass正在使用其构造函数进行初始化”,但在构造函数完成后,运行构造函数的线程以外的其他线程可能会在myPath中看到值null,而不是对字符串的引用。
在Java语言规范版本8的Java部分中有一个链接的示例,但这与JSR-133中发布的JMM保持不变
示例17.5-1.Java模型中的最终字段 下面的程序说明了最终字段与正常字段的比较。 类FinalFieldExample { int x;int y;静态FinalFieldExample f;公共FinalFieldExample() {x= 3;y= 4;}静态虚空写入器(){f=新的FinalFieldExample();}静态空白读取器(){ if (f != null) { int i= f.x;//保证看到3 int j= f.y;//可以看到0} 类FinalFieldExample有一个最终的int字段x和一个非最终的int字段y。一个线程可能执行方法编写器,另一个线程可能执行方法读取器。 因为编写方法在对象的构造函数完成后写入f,因此读者方法将确保看到F.X的正确初始化值:它将读取值3。然而,f.y不是最终的;因此,reader方法不能保证看到它的值4。
这种情况甚至可能发生在一台负载很重的机器上,它有许多线程。
解决办法/解决办法:
myPath成为一个final字段(并且不要在分配字段之前添加传递this引用的构造函数)myPath成为volatile字段myPath之前,确保访问myPath的所有线程在同一个监视器对象上同步。例如,通过使beginmt成为synchronized方法,或通过任何其他方法。发布于 2014-08-22 03:39:28
文件构造函数可能仍然看不到myPath的值吗?
答案是肯定的,因为Java内存模型只保证最后字段的可见性:-
“应该为初始化安全提供新的保证。如果一个对象被正确构造(这意味着在构造过程中对它的引用不会转义),那么所有看到该对象引用的线程也将看到构造函数中设置的其最终字段的值,而不需要同步。”。
JSR 133链路
然而,我觉得这种情况是不可能重现的(我之前也尝试过类似的理论,但以失败告终)。
构造函数中存在不安全发布/转义此引用的情况,这可能导致myPath未被正确初始化的场景。在您提到的书的清单3.7中给出了一个示例。下面是使类在构造函数中转义的引用的一个示例。
public class MultiThreadingClass implements Runnable{
public static volatile MultiThreadingClass unsafeObject;
private String myPath ="c:\\somepath";
public MultiThreadingClass() {
unsafeObject = this;
.....
}
public void beginmt(){
Holder h = new Holder();
h.setPath(new File(myPath)); //line 6
h.begin();
}
}即使在正确设置unsafeObject引用之前,上面的类也会导致其他线程访问myPath引用,但是重新创建这个场景可能会很困难。
https://stackoverflow.com/questions/25438681
复制相似问题