理解为什么在Java内存模型方面在构造函数内启动线程是不安全的[重复]

根据Java Concurrency in Practice,在类构造函数中启动一个线程是危险的。原因是this在完全构造对象之前,这会将指针暴露给另一个线程。

尽管在之前的许多StackOverflow问题中都讨论过这个主题,但我仍然难以理解为什么会出现这种问题。

让我举一个具体的例子说明我想做的事情。(此段代码的所需输出是将数字20打印到控制台。)

private static class ValueHolder {

    private int value;
    private Thread thread;

    ValueHolder() {
        this.value = 10;
        thread = new Thread(new DoublingTask(this)); // exposing "this" pointer!!!
        thread.start();   // starting thread inside constructor!!!
    }

    int getValue() {
        return value;
    }

    void awaitTermination() {
        try {
            thread.join();
        } catch (InterruptedException ex) {}
    }}private static class DoublingTask implements Runnable {

    private ValueHolder valueHolder;

    DoublingTask(ValueHolder valueHolder) {
        this.valueHolder = valueHolder;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName());
        System.out.println(valueHolder.getValue() * 2);   // I expect this to print out 20...
    }}public static void main(String[] args) {
    ValueHolder myValueHolder = new ValueHolder();
    myValueHolder.awaitTermination();}

是的,我知道线程是在我们从构造函数返回之前启动的。是的,我知道this指针暴露给线程。然而,我确信代码是正确的 - 我相信它总会将数字20打印到控制台。

  • 任务this.value = 10 发生在之前 thread.start()。(这是因为在执行顺序this.value = 10之前thread.start()。)

  • thread.start()主线程中的调用发生.run()在新创建的线程中方法开始之前

  • 在开始.run()的方法之前发生System.out.println(valueHolder.getValue() * 2);print语句。(再次,按执行顺序。)

因此,通过Java Memory Model,print语句应该读取正确初始化的值valueHolder.value(即10)。尽管忽略了Java Concurrency in Practice的建议,但我的代码应该可以正常工作。

我弄错了吗?我错过了什么?


12345678_0001
浏览 686回答 2
2回答

手掌心

假设我是你的类的子类。它可能没有在需要时初始化其字段。class BetterValueHolder extends ValueHolder{     private int betterValue;     BetterValueHolder(final int betterValue)     {         // not actually required, it's added implicitly anyway.         // just to demonstrate where your constructor is called from         super();         try         {             Thread.sleep(1000); // just to demonstrate the race condition more clearly         }         catch (InterruptedException e) {}         this.betterValue = betterValue;     }     @Override     public int getValue()     {         return betterValue;     }}这将打印为零,无论给予构造函数的值是什么BetterValueHolder。

慕的地10843

原因是在完全构造对象之前,这会将this指针暴露给另一个线程。在您的代码中,对象已经构造得足够(value已设置为10),因此它将按预期运行。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java