如何模拟构造函数的竞争条件?

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

这表明如果一个线程具有对共享对象的引用,那么其他线程可能能够在构造函数完成执行之前访问该对象。

我试图将其付诸实践,因此我编写了这段代码,认为如果我运行足够多次RuntimeException(“World is f * cked”)就会发生。 但事实并非如此。

这是Java规范的一个案例,不保证某些东西,但我特定的java实现保证了它吗? (java版本:Ubuntu上的1.5.0)或者我误读了书中的内容?

代码:(我期待一个例外,但它永远不会被抛出)

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

那么,你需要更多地了解这些问题。 任何事情都不是“保证”的情况并非如此。 对于并发问题,除非你真的做了特定的事情来强迫问题发生,否则什么都不能保证。 你只是依靠足够的运行应该产生的希望,而事实并非如此。 这些问题很难预测,这就是并发是一个难题。 你可以尝试在你的函数中做更多的工作,但我向你保证这些是运行时不会拯救你的真正问题。

在构造函数完成之后才发布引用,更改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()); } } 

这将永远不会抛出Widgit因为Widgit实例变量w在构造函数代码执行之前保持为null。 当你的主线程在Widgit构造函数中hibernate时,你的Checker实例会不断地遇到NullPointerException因为w变量仍为null。 当主线程完成构造时,Widgit中的两个int变量是相等的。