2016-04-09 53 views
-2

我試圖證明使用普通Map有多個並行任務的問題。下面的例子(編譯和運行)旨在展示Map失敗:顯示一個非併發地圖下的多線程打破

import java.util.*; 
import java.util.stream.*; 
import java.util.concurrent.*; 

class BreakMap2 implements Runnable { 
    private Map<Integer, Integer> map; 
    public BreakMap2(Map<Integer, Integer> map) { 
    this.map = map; 
    } 
    @Override 
    public void run() { 
    while(true) { 
     int key = ThreadLocalRandom.current().nextInt(10_000); 
     if(map.containsKey(key)) { 
     assert map.get(key) == key; 
     } 
     map.put(key, key); 
    } 
    } 
} 

public class MapBreaker2 { 
    public static void main(String[] args) { 
    Map<Integer, Integer> map = new HashMap<>(); 
    IntStream.range(0, 1000) 
     .mapToObj(i -> new BreakMap2(map)) 
     .map(CompletableFuture::runAsync) 
     .collect(Collectors.toList()) 
     .forEach(CompletableFuture::join); 
    } 
} 

這並不說明問題(它不會失敗)。我怎樣才能更有效地做到這一點?有沒有一種方法可以快速而可靠地失敗?

爲了澄清,我想表明它是如何的不安全有多個任務寫入未被設計爲同時使用一個Map。我正在嘗試創建一些由於併發訪問而顯示錯誤寫入Map的內容。

編輯:我已經簡化了這個例子,現在它只是運行直到你點擊Control-C。我想要的是沒有停止該計劃。

+0

看起來像它的使用大小爲1的線程池 – bdkosher

+1

你的例子看起來過於複雜。究竟想要展示什麼? –

+0

問題是什麼?你想要顯示一個非併發映射在多線程下不起作用嗎?這不是一個問題,而是一個碰撞牆受損的汽車 - 這是衆所周知的和記錄。 – TomTom

回答

1

你不清楚你試圖挑起哪種「不正確的寫」。

有各種各樣的,可以用數據結構的併發更新發生的問題。與HashMap不同步併發更新,這在實際應用中確實顯示了一個臭名昭著的問題,是HashMap.get被卡在一個無限循環,但是,當然,它運行無限反正你的程序將無法察覺這一點。

您正在測試的唯一的事是存儲Integerassert map.get(key) == key;該值不測試對象標識(否則它便註定要失敗的,由於自動裝箱值的未指定的對象標識),而是包含int值,即使其所有者對象是通過數據競賽發佈的,也會從final字段的擔保中受益。因此,您無法在此處看到未初始化的值,並且很難想象任何可能遇到錯誤值的情況。

既然你存儲一個不可改變的對象,其值是免疫的數據競爭,只有結構更新到Map本身也有影響。但這些都很少見。您正在使用010000之間的隨機密鑰填充Map,因此一旦遇到該範圍內的所有一萬個不同的密鑰,就不會再有結構變化。機會很好,即使在下一個任務開始工作之前,第一個異步任務也會達到該狀態。即使在重疊階段較短的情況下,在該時間窗內遇到數據競爭的可能性也很低。

在那短暫的時間窗口之後,您只是用對數據競爭免疫的對象替換現有映射的值,並且由於它們表示相同的裝箱值,所以JVM甚至可以優化整個更新。


如果您想要失敗的可能性較高的程序,您可以嘗試以下操作。它執行從一個Map到另一個的簡單天真轉移,探測條目並將其放入目標映射(如果存在)。它看起來很簡單,並且確實可以在線程數爲1的情況下順利運行,但在使用任何其他線程數時,在大多數環境中會出現嚴重故障。

public class MapBreaker2 { 
    public static void main(String[] args) throws InterruptedException { 
    int threadCount = 2; // try varying that number 
    Map<Integer, Integer> source = IntStream.range(0, 10_000) 
      .boxed().collect(Collectors.toMap(i->i, i->i)); 
    System.out.println("trying to copy "+source.size()+" mappings without synchonizing"); 
    Map<Integer, Integer> target = new HashMap<>(); 
    Callable<?> job=() -> { 
     while(!source.isEmpty()) { 
      int key = ThreadLocalRandom.current().nextInt(10_000); 
      Integer value=source.remove(key); 
      if(value!=null) 
       target.put(key, value); 
     } 
     return null; 
    }; 
    ExecutorService pool = Executors.newCachedThreadPool(); 
    pool.invokeAll(Collections.nCopies(threadCount, job)); 
    pool.shutdown(); 
    System.out.println(target.size()); 
    assert source.isEmpty(); 
    assert target.size()==10_000; 
    } 
} 

但要強調的是,不同步多線程仍然是不可預測的,所以它可以在沒有一個或另一個測試運行明顯的錯誤運行...

+0

謝謝......這可能會成功。我會調查並回復你。 – user1677663

+0

我無法得到隨機複印機來完成,但我能夠執行多個副本,並得到的效果: public void run(){ for(int key:src.keySet()) dest.put (key,src.get(key)); } 感謝您的幫助! – user1677663

+0

@ user1677663:永不完成是非同步程序的可能結果之一。一個可能的原因是源地圖大小的更新丟失,所以儘管沒有映射,它從來沒有被認爲是空的。 – Holger

相關問題