Java中的并发对象创建

No *_* QA 11 java concurrency multithreading object safe-publication

我正在读Brian Goetz的一本书"实践中的Java并发".第3.5和3.5.1段包含我无法理解的陈述.

请考虑以下代码:

public class Holder {
  private int value;
    public Holder(int value) { 
    this.value = value;
  }

  public void assertValue() {
    if (value != value) throw new AssertionError("Magic");
  }
}

class HolderContainer {
  // Unsafe publication
  public Holder holder;

  public void init() {
    holder = new Holder(42);  
  }
}
Run Code Online (Sandbox Code Playgroud)

作者说:

  1. 在Java中,Object构造函数首先在子类构造函数运行之前将默认值写入所有字段.
  2. 因此,可以将字段默认值视为过时值.
  3. 线程可能在第一次读取字段时看到陈旧值,然后在下次读取更新的值,这就是assertN可以抛出AssertionError的原因.

因此,根据文本,有些不幸的时机可能值= 0; 在下一刻,值= 42.

我同意第1点,Object构造函数首先使用默认值填充字段.但我不明白第2和第3点.

让我们更新作者代码并考虑以下示例:

public class Holder {
  int value;

  public Holder(int value) {
    //Sleep to prevent constructor to finish too early
    try {
     Thread.sleep(3000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    this.value = value;
  }

  public void assertValue()  {
    if(value != value) System.out.println("Magic");
  }
}
Run Code Online (Sandbox Code Playgroud)

我添加了Thread.sleep(3000),强制线程在完全构造对象之前等待.

public class Tests {

  private HolderContainer hc = new HolderContainer();

  class Initialization implements Runnable {
    public void run() {
      hc.init();
    }
  }

  class Checking implements Runnable {
    public void run() {
      hc.holder.assertValue();
    }
  }

  public void run() {
    new Thread(new Initialization()).start();
    new Thread(new Checking()).start();
  }
}
Run Code Online (Sandbox Code Playgroud)

例如:

  1. 第一个线程inits holder对象
  2. 第二个线程调用assertValue

主线程运行两个线程:

  1. new Thread(new Initialization()).start(); 完全构造Holder对象花了3秒钟
  2. new Thread(new Checking()).start(); 由于Holder对象仍未构造代码,因此将抛出NullPointerException

因此,当字段具有默认值时,不可能模拟情况.

我的问题:

  1. 作者错误的这个并发问题?
  2. 或者它无法模拟字段默认值的行为?

孙兴斌*_*孙兴斌 0

没有用您的代码重现它。这是一个模拟不安全发布的示例。该策略是让一个线程发布Holder并让另一个线程检查其值。

class Holder {
    private volatile int value;

    public Holder(int value, HolderContainer container) {
        container.holder = this;  // publication this object when it is not initilized properly
        try {
            Thread.sleep(10);  
        } catch (Exception e) {

        }
        this.value = value; // set value
    }

    public int getValue() {
        return value;
    }
}

class HolderContainer {

    public Holder holder;

    public Holder getHolder() { 
        if (holder == null) { 
            holder = new Holder(42, this);
        }
        return holder;
    }
}


public class Tests {

    public static void main(String[] args) {
        for (int loop = 0; loop < 1000; loop++) {
            HolderContainer holderContainer = new HolderContainer();
            new Thread(() -> holderContainer.getHolder()).start();
            new Thread(() -> {
                Holder holder = holderContainer.getHolder();
                int value1 = holder.getValue();  // might get default value
                try {
                    Thread.sleep(10);
                } catch (Exception e) {

                }
                int value2 = holder.getValue(); // might get custom value
                if (value1 != value2) {
                    System.out.println(value1 + "--->" + value2);
                }
            }).start();
        }
    }

}
Run Code Online (Sandbox Code Playgroud)