[英]How do I copy ArrayList<T> in java multi-threaded environment?
在線程A中,創建了一個ArrayList
。 它僅從線程A管理。 在線程B中,我想將其復制到新實例。
要求是copyList
不應該失敗並且應該返回列表的一致版本(=至少在復制過程中存在一段時間)。
我的方法是:
public static <T> ArrayList<T> copyList(ArrayList<? extends T> list) {
List<? extends T> unmodifiableList = Collections.unmodifiableList(list);
return new ArrayList<T>(unmodifiableList);
}
Q1:這是否滿足要求?
Q2:如果沒有Collections.unmodifiableList
並且可以使用pro-catch和try-catch塊,我怎么能這樣做呢?
UPD。 這是我一年前被問到的一個面試問題。 我理解在多線程環境中使用像ArrayList這樣的非線程安全集合是一個壞主意
否ArrayList
不是線程安全的,您沒有使用顯式synchronization
。
當您執行方法unmodifiableList時,第一個線程可以修改原始列表,並且您將擁有一個無效的不可修改列表。
我認為最簡單的方法如下:
例如,類似於:
List<T> l = Collections.synchronizedList(new ArrayList<T>());
...
public static <T> List<T> copyList(List<? extends T> list) {
List<T> copyList = null;
synchronized(list) {
copyList = new ArrayList<T>(list);
}
return copyList;
}
您應該同步對ArrayList
訪問,或者將ArrayList
替換為並發集合,如CopyOnWriteArrayList
。 如果不這樣做,您可能會得到一個不一致的副本。
如果 “擁有”線程不提供某些協議,則絕對無法創建普通 ArrayList的副本。
如果沒有任何協議,線程A可以隨時可能修改的列表,這意味着線程B 從來沒有得到一個機會,以確保是看到了列表的一致狀態。
為了實際允許進行一致的復制,線程A 必須確保它所做的任何修改都寫入內存並且對其他線程可見。
通常,允許VM在其認為合適時重新排序指令,讀取和寫入,前提是在執行程序的線程內沒有觀察到差異。 這包括,例如,通過在CPU寄存器或本地堆棧中保存值來延遲寫入。
確保所有內容始終寫入主要理論的唯一方法是,線程A執行向VM提供重新排序屏障的指令(例如,同步塊或易失性字段訪問)。
因此,如果沒有線程A的某些合作,就無法確保滿足上述條件。
繞過這種情況的常見方法是通過僅以安全包裝的形式(Collections.synchronizedCollection)使用它來同步對List的訪問,或者使用內置了這些保證的List實現(任何類型的並發列表實現)。
Collections.unmodifiableList(...)
的javadoc說,“返回指定列表的不可修改的視圖 。”
關鍵詞是“觀點”。 這意味着它不會復制數據。 它所做的只是為給定列表創建一個包裝器,其中包含所有拋出異常而不是修改基本列表的mutators。
是的,但我實際上創建了新的ArrayList(Collections.unmodif ...),這不會有用嗎?
哎呀! 我錯過了。 如果你要復制列表,那么調用unmodifiableList()
是沒有意義的。 將訪問不可修改視圖的唯一代碼是在創建它的同一方法中的代碼。 您不必擔心代碼修改列表內容,因為您編寫了它。
另一方面,如果您要在其他線程更新列表時復制列表,那么您將需要全部synchronized
。 代碼可以更新列表的每個地方都需要在synchronized
塊中,副本的代碼也是如此。 當然,所有這些同步塊必須在同一對象上同步。
一些程序員將使用列表對象本身作為鎖對象。 其他人則更喜歡使用單獨的對象。
Q1:這是否滿足要求?
如果您在使用復制它提供的列表中進行修改new ArrayList<T>(unmodifiableList)
你會得到一個ConcurrentModificationException
,即使你把它包使用Collections.unmodifiableList
因為Iterator
的的UnmodifiableList
只是調用Iterator
的包裝清單和這里它是一個非線程安全列表,您仍然可以獲得ConcurrentModificationException
。
您可以做的確實是使用CopyOnWriteArrayList
因為它是一個線程安全列表實現,當您嘗試迭代它時,它提供List
一致快照。 另一種方法可能是使用new ArrayList<T>(myList)
使Thread A定期為其他線程推送它的安全副本,因為它是修改它的唯一線程我們知道在創建副本時沒有其他線程會修改它所以這樣會很安全。
Q2:如果沒有
Collections.unmodifiableList
和可能的迭代器和try-catch塊,我怎么能這樣做呢?
如上所述, Collections.unmodifiableList
在這里沒有幫助它使線程安全,對我來說唯一可能有意義的事實恰恰相反:線程A(唯一可以修改列表的線程)創建了一個安全的ArrayList
副本使用new ArrayList<T>(list)
然后使用Collections.unmodifiableList(list)
將其未修改的列表推送到其他線程。
一般來說,您應該避免在方法的定義中指定實現,尤其是公共實現,您應該只使用接口或抽象類,否則您將向API的用戶提供實現細節,這是不期望的。 所以這里它應該是List
或Collection
而不是ArrayList
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.