我最近在一次采訪中被要求在 java 中實作 Map 類的執行緒安全實作,并假設不存在執行緒安全庫。
interface Map{
void put(String key, String value);
String get(String key);
String delete(string key);
}
我包裝了 HashMap 并使函式同步。這是我的實作:
class MyMap{
Map<String, String> map;
public MyMap(){
map = new HashMap<>();
}
public synchronized void put(String key, String value){
map.put(key, value);
}
public String get(String key){
return map.get(key);
}
public synchronized String delete(String key){
return map.remove(key);
}
}
雖然添加synchronized允許執行緒安全,但我真的不知道如何證明這是否正確。
我嘗試撰寫以下單元測驗,但無法真正說服我的地圖是執行緒安全的,因為我不知道 get 會先發生還是 put 會先發生。
Thread t1 = new Thread(()->{ map.put("testKey1", "testValue1") });
Thread t2 = new Thread(()->{ map.put("testKey2", "testValue2") });
t1.start();
t2.start();
Thread t3 = new Thread(()->{ System.out.println(map.get("testKey1")) });
Thread t4 = new Thread(()->{ System.out.println(map.get("testKey2")) });
t3.start();
t4.start();
我看到了 ConcurrentHashMap 類的實作,看起來他們使用了在面試中太復雜而無法分辨的 Segments。
有人可以建議我的實作是否是執行緒安全的,如果是的話如何進行測驗來證明它。
uj5u.com熱心網友回復:
有人可以建議我的實作是否是執行緒安全的......
您的實作不是執行緒安全的:
該
map變數未宣告為private final. 這意味著其他一些代碼可以進入并修改甚至替換被包裝的Map. 這(至少!)不是執行緒安全的。可以說,無論執行緒安全如何,它都會被破壞。該
get方法在map不同步的情況下訪問變數。這意味著在(比如說)一個寫入(通過或)的執行緒和從它讀取的第二個執行緒之間沒有關系之前發生。沒有發生之前的情況意味著不能保證寫入(總是)對讀取執行緒可見。這是一個執行緒安全錯誤。mapputdelete
后一個執行緒安全錯誤可能表現為get()請求:
- 獲取陳舊的值,或
- 沒有獲得應該存在的鍵的值,或者
- 獲取已洗掉鍵的值,或
- 意外的例外,或
- 無限回圈(!)。
使用您的MyMap類的應用程式可能在某些平臺上運行,而不是在其他平臺上運行。它可能會重復失敗,或者很少以幾乎不可能在測驗環境中重現的方式失敗。
....如果是的話,如何進行測驗來證明這一點。
您無法通過測驗來證明代碼是執行緒安全的。您只能通過分析代碼來證明某些東西是執行緒安全的。根據您需要的證明有多嚴格,即使這樣也可能很困難。
適當撰寫的測驗可以證明代碼不是執行緒安全的。基本上,您需要一些東西來表明可見性問題(見上文)實際上表現為不正確的行為。但問題是第二個缺陷涉及一種由于缺乏保證而引起的行為。讀操作不能保證看到之前的寫操作,但無論如何它可能會看到。這意味著您可以在損壞的實作上運行測驗并觀察到測驗在某些平臺上永遠不會失敗。簡而言之,測驗沒有失敗并不能證明任何事情。
您如何為上述行為撰寫測驗?
那么一個執行緒需要更新,MyMap第二個執行緒需要執行讀取。并且您需要以這樣一種方式設計測驗,即如果讀者看到不正確的get結果,測驗將失敗。但是設計這樣的測驗很困難,因為測驗本身可以觸發(例如)記憶體快取重繪 ,這會掩蓋您正在尋找的潛在效果。(例如,跟蹤列印可以做到這一點。)
uj5u.com熱心網友回復:
您的測驗應確保您不能兩次添加相同的元素,并且不能兩次洗掉相同的元素。在每種情況下,都應該在第二個執行緒中拋出例外,無論它是什么。我看不出有什么理由讓同步。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/424577.html
