我正在阅读“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");
}
}
}
}
答案 0 :(得分:3)
在构造函数完成之后才发布引用,更改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;
答案 1 :(得分:1)
嗯,你需要多了解这些问题。任何事情都不是“保证”的情况并非如此。对于并发问题,除非你真的做了特定的事情来强迫问题发生,否则没有什么能得到真正的保证。你只是依靠足够的运行应该产生的希望,但事实并非如此。这些问题很难预测,这就是并发是一个难题。你可以尝试在你的函数中做更多的工作,但我向你保证这些是运行时不会为你节省的实际问题。
答案 2 :(得分:1)
在睡觉之前,启动一个打印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());
}
}
答案 3 :(得分:0)
这将永远不会抛出RunTimeException
,因为在构造函数代码执行之前,Widgit
实例变量w
保持为空。当您的主要帖子在Widgit
构造函数中休眠时,Checker
实例会不断地NullPointerException
,因为w
变量仍然为空。当主线程完成构造时,Widgit中的两个int
变量是相等的。