如何证明未正确发布的值的竞争条件?

时间:2010-04-12 19:06:37

标签: java concurrency

我正在阅读“Java Concurrency in practice”并查看第51页的示例代码。

根据该书,如果没有正确发布,这段代码就有失败的风险。因为我喜欢编写示例并打破它们来证明它们是如何工作的。我试图让它抛出AssertionError但失败了。 (引导我到我的previous question

任何人都可以发布示例代码,以便抛出AssertionError吗?规则:不要修改持有人类。

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 个答案:

答案 0 :(得分:3)

我会在多处理器机器上运行几个小时,看看会发生什么(如果使用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 :(得分:1)

正如BobbyShaftoe在另一个帖子中所说,你不能仅依赖于运行代码足够的时间来证明错误是否可能发生。如果你从汇编级别考虑这个问题,对于n!= n来说将是非常困难的,因为它是如此少的调用并且依赖于在非常精确的时间切换出的进程。

如果您希望能够显示并发系统是否具有可证明的有效性,那么最好使用Labeled Transition Systems之类的模型对其进行建模。如果您有兴趣证明并发或发现错误,请尝试使用LTSA工具。

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

答案 2 :(得分:1)

在该示例中,该书给予Holder类并不直接导致问题,实际上它表明:

这里的问题不是Holder类本身,而是Holder没有正确发布。但是,通过宣布n字段是最终的,Holder可以免于不正当的出版物,这将使Holder不变;请参见第3.5.2节。

就在此之前,它提到了以下代码,它是问题的主题:

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

因此,要重新创建它,您需要创建一个发布者类和两个线程,一个调用initialize,另一个调用assert。

话虽如此,我试图自己重新创建它仍然没有这样做:(

以下是我的第一次尝试,但是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);
    }

    }
}

答案 3 :(得分:0)

我认为如果不修改Holder类就不会发生断言错误。我认为这本书是错误的。

导致断言错误的唯一原因是在部分构造的对象上调用assertSanity()时。除了构造函数线程之外,线程如何引用部分构造的对象? AFAIK,只有以下两种情况才有可能:

  1. 在构造函数中发布this。例如。将this分配给共享变量。这在我们的示例代码中不会发生,因为Holder的构造函数不会这样做。
  2. 类的非静态内部类可以引用其父类,即使其父元素是部分构造的。这不可能发生,因为Holder没有任何内部类。
  3. 请注意,本书中的以下代码不会发布任何部分构造的对象:

    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 holderinvokespecial <init>之后执行。这意味着holder的赋值在构造函数完成后发生。部分构造的对象仅存储在线程的堆栈中。它没有发表。

    如果您能以合理的方式触发断言错误(例如反射不合理),请将其放在此处。我会投票给你。

答案 4 :(得分:-1)

您无法通过以下方式随时更改n的值:

  Holder h = new Holder(5);
  Field f = h.getClass().getDeclaredField("n");
  f.setAccessible(true);
  f.setInt(h, 10);
  h.assertSanity();