Создание объекта параллелизма в Java - PullRequest
0 голосов
/ 02 июня 2018

Я читаю книгу Брайана Гетца "Параллельность 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);  
  }
}

Автор утверждает, что:

  1. В Java,Конструктор объекта сначала записывает значения по умолчанию во все поля перед запуском конструктора подкласса.
  2. Следовательно, значение по умолчанию для поля можно рассматривать как устаревшее значение.
  3. Поток может видеть устаревшее значение при первом чтении поля, а затем более актуальное значение в следующий раз.Вот почему assertN может выдать AssertionError.

Итак, согласно тексту, при некоторой неудачной синхронизации возможно, что значение = 0;и в следующий момент значение = 42.

Я согласен с пунктом 1, что конструктор объекта сначала заполняет поля значениями по умолчанию.Но я не понимаю пунктов 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");
  }
}

Я добавил 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();
  }
}

Например:

  1. первый поток входит в объект-держатель
  2. второй поток вызывает assertValue

Основной поток запускает два потока:

  1. новый поток (новая инициализация ()). Start ();Требуется 3 секунды, чтобы полностью построить объект Holder
  2. new Thread (new Checking ()). Start ();поскольку объект Holder, который еще не создан, код выдаст NullPointerException

Поэтому невозможно эмулировать ситуацию, когда поле имеет значение по умолчанию.

Мои вопросы:

  1. Автор был неправ по поводу этой проблемы параллелизма?
  2. Или невозможно эмулировать поведение для значений полей по умолчанию?

Ответы [ 3 ]

0 голосов
/ 02 июня 2018

Чтобы ответить на первый вопрос: автор прав -

возможно несоответствие, поскольку у вас есть (несинхронизированное) взаимодействие между двумя потоками через общий объект Holder.

Здесь (теоретически) поток чтения может наблюдать за операциями, происходящими в другом порядке относительно потока записи;потому что никакие happens-before отношения не навязываются.Для обеспечения happens-before вы должны использовать синхронизацию или переменные переменные, среди прочего.См. Раздел JLS: 17.4.5. Происходит перед порядком .

Порядок операций, видимый потоком записи:

(call the constructor of the holder object)
assign field value=42
assign the constructed object the the holder field

Поток читателя мог наблюдать:

(call the constructor of the holder object)
assign the constructed object the the holder field
> at this point, it could read the uninitialized value
assign value=42

0 голосов
/ 02 июня 2018

Вы, вероятно, пытаетесь смоделировать сценарий параллелизма, который, как мне кажется, очень сложно смоделировать с использованием пары потоков.

Следующий написанный вами тестовый пример не совсем корректен, более вероятенбросить NullPointerException.

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(); 
  }
}

Что если ваш поток проверки выполняется до инициализации?Кроме того, установка режима сна просто означает, что выполняющийся поток будет находиться в режиме ожидания и сообщит вам о внутренних элементарных операциях, выполняемых к тому времени.

0 голосов
/ 02 июня 2018

Не воспроизводил его с вашим кодом.Вот пример для подражания небезопасной публикации.Стратегия заключается в том, чтобы одна публикация потока 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();
        }
    }

}
...