[英]Proving the following code not thread safe
如何通過編寫一些代碼來快速證明以下類不是線程安全的(因為它使用惰性初始化而不使用同步)? 換句話說,如果我正在測試以下類的線程安全性,我怎么能失敗呢?
public class LazyInitRace {
private ExpensiveObject instance = null;
public ExpensiveObject getInstance() {
if (instance == null)
instance = new ExpensiveObject();
return instance;
}
}
根據定義,除非您控制線程調度程序(您沒有),否則無法確定性地測試競爭條件。 您可以做的最接近的事情是在getInstance()
方法中添加可配置的延遲,或者編寫問題可能顯示的代碼並在循環中運行數千次。
順便說一句,這些都不構成“證明”。 即使對於相對少量的代碼, 正式驗證也會非常非常困難。
你可以強迫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 | |
由於這是Java,因此您可以使用thread-weaver庫在代碼中注入暫停或中斷並控制多個執行線程。 這樣你就可以獲得一個緩慢的ExpensiveObject
構造函數,而不必修改構造函數代碼,正如其他人(正確)建議的那樣。
嗯...這段代碼的結果將是假的,你期望的是真的。
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) {
}
}
}
在構造函數中進行一個非常長的計算:
public ExpensiveObject()
{
for(double i = 0.0; i < Double.MAX_VALUE; ++i)
{
Math.pow(2.0,i);
}
}
如果MAX_VALUE
花費太長時間,您可能希望將終止條件降低到Double.MAX_VALUE/2.0
或除以更大的數字。
您可以使用調試器輕松證明它。
這樣做的好處是你實際上並不需要ExpensiveObject,任何Object實際上都會產生相同的結果。 您只是使用調試器來安排執行特定代碼行,從而創建確定性結果。
好吧,它不是線程安全的。 線程安全性的證明是隨機的,但相當簡單:
使ExpensiveObject構造函數完全安全:
synchronized ExpensiveObject(){...
放置構造函數代碼,檢查是否存在另一個對象副本 - 然后引發異常。
創建線程安全方法以清除'instance'變量
將getInstance / clearInstance的順序代碼放入循環以供多個線程執行並等待(2)異常
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.