Don*_* Ch 12 java multithreading thread-safety
如何通过编写一些代码来快速证明以下类不是线程安全的(因为它使用惰性初始化而不使用同步)?换句话说,如果我正在测试以下类的线程安全性,我怎么能失败呢?
public class LazyInitRace {
private ExpensiveObject instance = null;
public ExpensiveObject getInstance() {
if (instance == null)
instance = new ExpensiveObject();
return instance;
}
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 13
您可以强制ExpensiveObject在测试中花费很长时间来构建吗?如果是这样,只需getInstance()从两个不同的线程中调用两次,在足够短的时间内第一个构造函数在第二次调用之前就不会完成.您将最终构建两个不同的实例,这是您应该失败的地方.
天真的双重检查锁定失败将会更加困难,请注意......(即使没有指定volatile变量也不安全).
这不是使用代码,但这是我如何证明它的一个例子.我忘记了这样的执行图的标准格式,但其含义应该足够明显.
| Thread 1 | Thread 2 |
|-----------------------|-----------------------|
| **start** | |
| getInstance() | |
| if(instance == null) | |
| new ExpensiveObject() | |
| **context switch ->** | **start** |
| | getInstance() |
| | if(instance == null) | //instance hasn't been assigned, so this check doesn't do what you want
| | new ExpensiveObject() |
| **start** | **<- context switch** |
| instance = result | |
| **context switch ->** | **start** |
| | instance = result |
| | return instance |
| **start** | **<- context switch** |
| return instance | |
Run Code Online (Sandbox Code Playgroud)
嗯...这段代码的结果将是假的,而你期望的结果是真。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class LazyInitRace {
public class ExpensiveObject {
public ExpensiveObject() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
private ExpensiveObject instance = null;
public ExpensiveObject getInstance() {
if (instance == null)
instance = new ExpensiveObject();
return instance;
}
public static void main(String[] args) {
final LazyInitRace lazyInitRace = new LazyInitRace();
FutureTask<ExpensiveObject> target1 = new FutureTask<ExpensiveObject>(
new Callable<ExpensiveObject>() {
@Override
public ExpensiveObject call() throws Exception {
return lazyInitRace.getInstance();
}
});
new Thread(target1).start();
FutureTask<ExpensiveObject> target2 = new FutureTask<ExpensiveObject>(
new Callable<ExpensiveObject>() {
@Override
public ExpensiveObject call() throws Exception {
return lazyInitRace.getInstance();
}
});
new Thread(target2).start();
try {
System.out.println(target1.get() == target2.get());
} catch (InterruptedException e) {
} catch (ExecutionException e) {
}
}
}
Run Code Online (Sandbox Code Playgroud)