Как продемонстрировать условия гонки вокруг значений, которые не опубликованы должным образом? - PullRequest
9 голосов
/ 12 апреля 2010

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

Согласно книге, этот фрагмент кода может быть с ошибкой, если он не был опубликован должным образом. Потому что я люблю кодировать примеры и ломать их, чтобы доказать, как они работают. Я пытался заставить его бросить AssertionError, но не удалось. (Ведет меня к моему предыдущему вопросу )

Может кто-нибудь опубликовать пример кода, чтобы генерировалась ошибка AssertionError? Правило: не изменяйте класс Holder.

public class Holder{
    private int n;

    public Holder(int n){
        this.n = n;
    }

    public void assertSanity(){
        if (n != n) {
            throw new AssertionError("This statement is false");
        }
    }
}

Я изменил класс, чтобы сделать его более хрупким, но все еще не могу получить брошенную ошибку AssertionError.

class Holder2 {
    private int n;
    private int n2;

    public Holder2(int n) throws InterruptedException{
        this.n = n;
        Thread.sleep(200);
        this.n2 = n;
    }

    public void assertSanity(){
        if (n != n2) {
            throw new AssertionError("This statement is false");
        }
    }
}

Можно ли заставить любой из вышеперечисленных классов генерировать ошибку AssertionError? Или мы должны признать, что они могут иногда это делать, и мы не можем написать код, чтобы доказать это?

Ответы [ 5 ]

3 голосов
/ 12 апреля 2010

Я бы запустил это на многопроцессорной машине в течение нескольких часов и посмотрел, что произойдет (отключите режим сна, если вы используете свой Holder2). Такие условия гонки могут быть редкими или не существовать на вашей конкретной машине - но, по крайней мере, попытайтесь спровоцировать их на миллион случаев, пытаясь миллионы раз.

class Checker {
  private Holder h;
  public Checker() {
   h = new Holder(42);
  }

  public void check() {
    h.assertSanity();
  }

  public void create(int n) {
   h = new Holder(n);
   }

}

public class MyThread extends thread{
  private bool check;
  private final Checker c;
  public MyThread(bool check,Checker c) {
    this.check = check;
    this.c = c;
  }
    public static void main(String[] args) {
      Checker c = new Checker();
      MyThread t1 = new MyThread(false,c);  
      MyThread t2 = new MyThread(true,c);
      t1.start();
      t2.start();
      t1.join();
      t2.join();
   }
   public void run() {
     int n = 0;
     while(true) {
       if(check) 
         c.check();
       else
         c.create(n++);
    }
   }
 }
}
1 голос
/ 11 марта 2011

В примере, который эта книга дает классу Холдера, не является непосредственно причиной проблемы, фактически говорится, что:

Проблема здесь не в самом классе Holder, а в том, что Holder не опубликован должным образом. Тем не менее, Holder может быть защищен от неправильной публикации, если объявить поле n окончательным, что сделает Holder неизменным; см. раздел 3.5.2.

Непосредственно перед этим упоминается следующий код, который является предметом проблемы:

// Unsafe publication
public Holder holder;
public void initialize() {
  holder = new Holder(42);
}

Таким образом, чтобы воссоздать его, вам нужно создать класс издателя и два потока, один из которых вызывает инициализацию, а другой - вызов утверждения.

Сказав это, я попытался воссоздать его сам, но все равно не смог :(

Ниже моя первая попытка, но есть лучшее объяснение проблемы на http://forums.oracle.com/forums/thread.jspa?threadID=1140814&tstart=195

public class HolderTest {

    @Test
    public void testHolder() throws Exception {
    for (int i = 0; i < 1000000000; i++) {
        final CountDownLatch finished = new CountDownLatch(2);

        final HolderPublisher publisher = new HolderPublisher();

        final Thread publisherThread = new Thread(new Publisher(publisher,
            finished));
        final Thread checkerThread = new Thread(new Checker(publisher,
            finished));

        publisher.holder = null;

        publisherThread.start();
        checkerThread.start();

        finished.await();
    }
    }

    static class Publisher implements Runnable {

    private final CountDownLatch finished;
    private final HolderPublisher publisher;

    public Publisher(final HolderPublisher publisher,
        final CountDownLatch finished) {
        this.publisher = publisher;
        this.finished = finished;
    }

    @Override
    public void run() {
        try {
        publisher.initialize();
        } finally {
        finished.countDown();
        }
    }

    }

    static class Checker implements Runnable {

    private final CountDownLatch finished;
    private final HolderPublisher publisher;

    public Checker(final HolderPublisher publisher,
        final CountDownLatch finished) {
        this.publisher = publisher;
        this.finished = finished;
    }

    @Override
    public void run() {
        try {
        publisher.holder.assertSanity();
        } catch (final NullPointerException e) {
        // This isnt the error we are interested in so swallow it
        } finally {
        finished.countDown();
        }
    }

    }

    static class HolderPublisher {

    // Unsafe publication
    public Holder holder;

    public void initialize() {
        holder = new Holder(42);
    }

    }
}
1 голос
/ 12 апреля 2010

Как сказал BobbyShaftoe в другом потоке, нельзя полагаться на то, что достаточно просто запустить код достаточно, чтобы показать, что ошибка может или не может произойти. Если вы подумаете об этом на уровне сборки, это будет очень трудно для n! = N, так как это очень мало вызовов и зависит от процесса, который переключается в действительно точное время.

Если вы хотите показать, является ли параллельная система достоверно достоверной, было бы лучше смоделировать ее, используя что-то вроде маркированных систем переходов. Попробуйте инструмент LTSA, если вы заинтересованы в проверке параллелизма или нахождении ошибок.

http://www.doc.ic.ac.uk/ltsa/

0 голосов
/ 30 сентября 2013

Не думаю, что ошибка утверждения может произойти без изменения класса Holder. Я думаю, что книга не права.

Единственная причина, по которой вызывается ошибка утверждения, - это когда assertSanity () вызывается для частично построенного объекта . Как может поток, кроме потока конструктора, ссылаться на частично созданный объект? AFAIK, это возможно только в следующих двух случаях:

  1. Опубликовать this в конструкторе. Например. присвойте this общей переменной. В нашем примере кода этого не может быть, потому что конструктор Holder этого не делает.
  2. Нестатический внутренний класс класса может ссылаться на своего родителя, даже если его родитель частично создан. Этого также не может быть, потому что у Холдера нет внутреннего класса.

Обратите внимание, что следующий код в книге не публикует частично построенный объект:

public class GoodCode {
  public Holder holder;
  public void initialize () {
    holder = new Holder(42);
  }
}

Если вы разберете initialize(), вы получите следующее:

public void initialize();
  Code:
   0: aload_0       
   1: new           #2                  // class Holder
   4: dup           
   5: bipush        42
   7: invokespecial #3                  // Method Holder."<init>":(I)V
  10: putfield      #4                  // Field holder:LHolder;
  13: return        

Обратите внимание, что putfield holder выполняется после invokespecial <init>. Это означает, что присвоение holder происходит после завершения конструктора. Частично построенный объект хранится только в стеке потока. Это не опубликовано.

Если вы можете вызвать ошибку подтверждения разумным способом (например, отражение не является разумным), укажите это здесь. Я буду голосовать за тебя.

0 голосов
/ 12 апреля 2010

Вы не можете изменить значение n в любое время, используя:

  Holder h = new Holder(5);
  Field f = h.getClass().getDeclaredField("n");
  f.setAccessible(true);
  f.setInt(h, 10);
  h.assertSanity();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...