为不安全发布的java.lang.String提供恢复时间

时间:2014-08-22 02:23:02

标签: java multithreading concurrency thread-safety safe-publication

java.lang.String只是有效的不可变的。 “Java Concurrency in Practice”中的Brian Goetz表示,如果安全发布,有效的不可变对象只会是线程安全的。现在,假设我不安全地发布这样的字符串:

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行的File构造函数可能看不到myPath的值。

然后,在构建不安全发布的String对象大约三秒后,MultiThreadingClass上的线程仍在运行。文件构造函数是否仍有可能看不到myPath的值?

2 个答案:

答案 0 :(得分:2)

您声明的问题是:

  

目前MultiThreadingClass正在使用它进行初始化   构造函数,可能会发生第6行的File构造函数   没有看到myPath的值。

答案很复杂。 您无需担心value对象中的char-array String。正如我在评论中提到的那样,因为它是在构造函数中分配的final字段,并且因为String在分配final字段之前未传递对自身的引用,所以始终安全发布。您也不必担心hashhash32字段。它们没有安全发布,但它们只能具有值0或有效的哈希码。如果它们仍为0,则方法String.hashCode将重新计算该值 - 它只会导致其他线程在之前已在其他线程中完成时重新计算hashCode。

myPath中的参考 MultiThreadingClass未安全发布,因为它不是final。 “目前MultiThreadingClass正在使用其构造函数初始化”,但稍后,在构造函数完成之后,除了运行构造函数的线程之外的其他线程可能会在null中看到值myPath而不是引用你的字符串。

Java Language Specification [版本8链接的Java内存模型部分中有一个示例,但自JMM在JSR-133中发布以来没有改变]:

  

例17.5-1。 Java内存模型中的最终字段

     

下面的程序说明了最终字段与普通字段的比较。

class FinalFieldExample { 
    final int x; 
    int y; 

    static FinalFieldExample f; 

    public FinalFieldExample() { 
        x = 3; 
        y = 4; 
    } 

    static void writer() { 
        f = new FinalFieldExample(); 
    } 

    static void reader() { 
        if (f != null) { 
            int i = f.x; // guaranteed to see 3 
            int j = f.y; // could see 0 
        } 
    } 
}
     

FinalFieldExample类有一个最终的int字段x和一个非final字段   int field y。一个线程可能会执行方法编写器和另一个   可能会执行方法阅读器。

     

因为writer方法在对象的构造函数之后写入f   完成后,读者方法将保证正确看到   f.x的初始化值:它将读取值3.但是,f.y是   不是最终的;因此,读者方法无法保证看到   价值4。

这甚至可能发生在拥有许多线程的负载很重的机器上。

解决方法/解决方案:

  • myPath设为final字段(并且在分配字段之前不要添加传递this引用的构造函数)
  • myPath设为volatile字段
  • 在访问myPath之前,确保访问myPath的所有线程在同一个监视器对象上同步。例如,通过beginmt synchronized方法,或通过任何其他方式。

答案 1 :(得分:1)

文件构造函数是否仍有可能看不到myPath的值?

答案是肯定的,因为Java Memory Model只保证最终字段的可见性 : -

"应提供初始化安全的新保证。如果一个对象被正确构造(这意味着对它的引用在构造期间不会被转义),那么看到对该对象的引用的所有线程也将看到在构造函数中设置的最终字段的值,而不需要。同步"

JSR 133 Link

然而,我觉得这种情况是不可能再创造的(我之前也曾尝试过类似的理论,但却徒劳无功)。

在构造函数中存在这种引用的不安全发布/转义的情况,这可能导致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();
    }
}

即使在正确设置myPath之前,上述类也可能导致其他线程访问unsafeObject引用,但再次重新创建此场景可能很困难。