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

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

根据该书,如果没有正确发布,这段代码就有失败的风险。 因为我喜欢编写示例并打破它们以certificate它们的工作原理。 我试图让它抛出一个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? 或者我们是否必须接受他们偶尔会这样做而且我们不能编写代码来certificate它?

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

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

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

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

在该示例中,该书给予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); } } } 

我不认为在不修改Holder类的情况下会发生断言错误。 我觉得这本书错了。

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

  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."":(I)V 10: putfield #4 // Field holder:LHolder; 13: return 

请注意, putfield holderinvokespecial 之后执行。 这意味着holder的分配发生在构造函数完成之后。 部分构造的对象仅存储在线程的堆栈中。 它没有发表。

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

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

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