2014-11-04 145 views
5

我試圖更新一些代碼來使用lambda表達式,但我在保留線程安全方面遇到了一些麻煩。使用lambda保留線程安全性

我有多個線程正在運行,最終調用下面的回調函數,其中有一個​​方法,它將一些結果添加到LinkedList

final List<Document> mappedDocs = new LinkedList<>(); 
final MapCallback<Integer, Document> mapCallback = new MapCallback<Integer, Document>() { 
    @Override 
    public synchronized void done(int file, List<Document> results) { 
     mappedDocs.addAll(results); 
    } 
}; 

然而,當我將它轉換爲Lambda表達式我失去了​​關鍵字,我不完全知道如何把它找回來。每當我運行我的代碼時,我現在都會收到一個NullPointerException異常。

final MapCallback<Integer, Document> mapCallback = (int file, List<Document> results) -> mappedDocs.addAll(results); 

我該如何讓這個線程再次安全?

+1

你不能不使用拉姆達? – jtahlborn 2014-11-04 20:28:21

+0

@jtahlborn我可以。這只是我的IDE告訴我,我可以用lambda取代它,所以我想我會嘗試。 – karoma 2014-11-04 20:29:53

+0

我打算使用[no](http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27)(請參閱不參考「同步」在那裏)。 – jtahlborn 2014-11-04 20:34:58

回答

7

你可以在不同的顯示器上同步,例如:

final MapCallback<Integer, Document> mapCallback = (int file, List<Document> results) -> { 
    synchronized(mappedDocs) { 
    mappedDocs.addAll(results); 
    } 
}; 

或者選擇使用一個線程安全的結構,例如一個的CopyOnWriteArrayList或BlockingQueue的。

3

我強烈建議使mappedDocs成爲一個線程安全的數據結構(如java.util.concurrent)或者使用Collections.synchronizedList創建同步包裝。

我認爲你很幸運,同步使用匿名內部類。這是有效的,因爲它只有一個實例,並且沒有其他代碼會發生變化mappedDocs

(其實你可能有一個存儲可見性問題,即使目前情況來看,如果其他線程調用MapCallback添加元素,別的東西需要對mappedDocs讀取添加的元素及其施工後和以前的同步。)

問題的根源在於,以這種方式使用的匿名內部類有點像函數,但是由於新對象的創建是明顯的,所以很容易做類似於同步的事情。但這是相當脆弱的。如果重構了AIC的多個實例(例如,處理來自多個來源的文檔),或者創建了不同的AIC(例如,如果需要重新處理時從列表中刪除文檔),則重新構造這些AIC實例,同步各個AIC實例將被徹底打破。

mappedDocs轉換爲線程安全的數據結構或包裝器負責內存可見性問題和併發訪問問題。這使您可以使用簡單的lambda表單,並且它可以讓您在mappedDocs上引入新的操作,而不必考慮在其上運行的線程。