Как смоделировать условия гонки конструктора?

Я читаю "Параллелизм Java на практике" и смотрю пример кода на странице 51.

Это говорит о том, что если поток имеет ссылки на общий объект, то другие потоки могут иметь доступ к этому объекту до того, как конструктор завершит выполнение.

Я попытался применить это на практике и поэтому написал этот код, думая, что, если я запустил его достаточно раз, возникнет исключение RuntimeException("World is f*cked"). Но это не так.

Это тот случай, когда спецификация Java не гарантирует что-то, а моя конкретная реализация Java гарантирует это для меня? (версия Java: 1.5.0 в Ubuntu) Или я что-то неправильно прочитал в книге?

Код: (я ожидаю исключения, но оно никогда не выбрасывается)

public class Threads {
 private Widgit w;

 public static void main(String[] s) throws Exception {
  while(true){
   Threads t = new Threads();
   t.runThreads();
  }
 }

 private void runThreads() throws Exception{
  new Checker().start();
  w = new Widgit((int)(Math.random() * 100)  + 1);
 }

 private class Checker extends Thread{
  private static final int LOOP_TIMES = 1000;

  public void run() {
   int count = 0;
   for(int i = 0; i < LOOP_TIMES; i++){
    try {
     w.checkMe();
     count++;
    } catch(NullPointerException npe){
     //ignore
    }
   }
   System.out.println("checked: "+count+" times out of "+LOOP_TIMES);
  }
 }

 private static class Widgit{
  private int n;
  private int n2;

  Widgit(int n) throws InterruptedException{
   this.n = n;
   Thread.sleep(2);
   this.n2 = n;
  }

  void checkMe(){
   if (n != n2) {
    throw new RuntimeException("World is f*cked");
   }
  }
 }

}

4 ответа

Решение

Ну, вам нужно понять проблемы немного больше. На самом деле это не тот случай, когда что-то было или не было "гарантировано". В случае проблем с параллелизмом, ничто не может быть гарантировано, если вы действительно не сделаете определенные вещи, чтобы заставить проблему возникнуть Вы просто полагаетесь на надежду, что должно произойти достаточное количество пробежек, а это не так. Подобные проблемы трудно предсказать, поэтому параллелизм - сложная проблема. Вы можете попытаться выполнить больше работы над своими функциями, но я вас уверяю, это реальные проблемы, от которых среда выполнения не спасет вас.

Вы не публикуете ссылку, пока конструктор не завершит работу, измените Widgit как это:

private class Widgit{ // NOTE: Not class is not static anymore
    private int n;
    private int n2;

    Widgit(int n) throws InterruptedException{
        this.n = n;
        w = this; // publish reference
        Thread.sleep(2);
        this.n2 = n;
    }

    void checkMe(){
        if (n != n2) {
        throw new RuntimeException("World is f*cked");
    }    
}

Должен теперь бросить.

Изменить: Вы должны также объявить Widgit поле как volatile:

 private volatile Widgit w;

Перед сном запустите новый поток, который печатает значение n2. Вы увидите, что второй поток может получить доступ к объекту до завершения конструктора.

Следующий пример демонстрирует это на Sun JVM.

/* The following prints
Incomplete initialisation of A{n=1, n2=0}
After initialisation A{n=1, n2=2}
 */
public class A {
    final int n;
    final int n2;
    public A() throws InterruptedException {
        n = 1;
        new Thread(new Runnable() {
            public void run() {
                System.out.println("Incomplete initialisation of " + A.this);
            }
        }).start();
        Thread.sleep(200);
        this.n2 = 2;
    }
    @Override
    public String toString() {
        return "A{" + "n=" + n + ", n2=" + n2 + '}';
    }
    public static void main(String... args) throws InterruptedException {
        System.out.println("After initialisation " + new A());
    }
}

Это никогда не бросит RunTimeException потому что ваш Widgit переменная экземпляра w остается нулевым, пока код конструктора не будет выполнен. Пока ваш главный поток спит в Widgit конструктор, ваш Checker экземпляр поражает NullPointerException постоянно как w переменная по-прежнему равна нулю. Когда ваш основной поток завершает строительство, два int переменные в виджите равны.

Другие вопросы по тегам