代码之家  ›  专栏  ›  技术社区  ›  andy boot

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

  •  9
  • andy boot  · 技术社区  · 16 年前

    我正在阅读“实践中的Java并发”,并查看第51页上的示例代码。

    根据这本书的说法,如果这段代码没有正确发布,它就有失败的风险。因为我喜欢编写示例并将其分解以证明它们是如何工作的。我试图让它抛出一个断言错误,但失败了(带我到我的 previous question

    有人能发布示例代码以便抛出断言错误吗?规则:不要修改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");
            }
        }
    }
    

    我已经修改了类,使它更脆弱,但我仍然不能得到一个断言错误抛出。

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

    是否有可能使上述任何一个类抛出断言错误?或者我们必须接受他们偶尔会这样做,而我们却不能编写代码来证明这一点吗?

    5 回复  |  直到 9 年前
        1
  •  3
  •   leeeroy    16 年前

    我会在多处理器机器上运行几个小时,看看会发生什么(如果您使用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++);
        }
       }
     }
    }
    
        2
  •  1
  •   Oli    16 年前

    正如BobbyShaftoe在另一篇文章中所说的,您不能仅仅依靠运行代码足够的次数来显示错误可能发生或不可能发生。如果您从程序集级别来考虑这个问题,那么对于n!=n因为呼叫太少,并且依赖于进程在一个非常精确的时间被关闭。

    如果您希望能够显示并发系统是否可证明有效,那么最好使用标记的转换系统之类的方法对其进行建模。如果您对证明并发性或查找错误感兴趣,请尝试LTSA工具。

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

        3
  •  1
  •   nhahtdh Pankaj Wadhwa    14 年前

    这里的问题不是Holder类本身,而是Holder没有正确发布。但是,通过声明n字段为final,可以使Holder不受不适当发布的影响,这将使Holder不可变;见第3.5.2节。

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

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

    因此,要重新创建它,您需要创建一个publisher类和两个线程,一个调用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);
        }
    
        }
    }
    
        4
  •  0
  •   Wu Yongzheng    13 年前

    我不认为断言错误可以在不修改 Holder 班级。我认为这本书错了。

    导致断言错误的唯一原因是在 部分构造对象 . 除了构造函数线程,线程如何引用部分构造的对象?好吧,只有在以下两种情况下才有可能:

    1. this 在构造函数中。例如,分配 这 共享变量。在我们的示例代码中不能发生这种情况,因为Holder的构造函数不会这样做。
    2. 一个类的非静态内部类可以引用它的父类,即使它的父类是部分构造的。这也不可能发生,因为Holder没有任何内部类。

    请注意,本书中的以下代码不会发布任何部分构造的对象:

    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 在构造函数完成后发生。部分构造的对象只存储在线程的堆栈中。它没有出版。

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

        5
  •  -1
  •   Igor Artamonov    16 年前

    不能更改的值 n 随时使用:

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