多線程中使用Java集合類
Java集合類中,某個線程在 Collection 上進行迭代時,通常不允許另一個線性修改該 Collection。通常在這些情況下,迭代的結果是不確定的。如果檢測到這種行為,一些迭代器實現(包括 JRE 提供的所有通用 collection 實現)可能選擇拋出此異常。執行該操作的迭代器稱為快速失敗 迭代器,因為迭代器很快就完全失敗,而不會冒著在將來某個時間任意發生不確定行為的風險。
因此,當一個線程試圖ArrayList的數據的時候,另一個線程對ArrayList在進行迭代的,會出錯,拋出ConcurrentModificationException。
比如下面的代碼:
|
上述程序運行后,會在某處拋出異常:
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at mytest.mytestpkg.Tj$2.run(Tj.java:138)
Vector是線程同步的,那么把ArrayList改成Vector是不是就對了呢?
答案是否定的,事實上,無論是ArrayList還是Vector,只要是實現Collection接口的,都要遵循fail-fast的檢測機制,即在迭代是時候,不能修改集合的元素。一旦發現違法這個規定就會拋出異常。
--------------------------------------------------------------------------------
事實上,Vector相對于ArrayList的線程同步,體現在對集合元素是否臟讀上。即ArrayList允許臟讀,而Vector特殊的機制,不會出現臟讀,但是效率會很差。
舉個例子,一個集合,有10個線程從該集合中刪除元素,那么每個元素只可能由一個線程刪除掉,不可能會出現一個元素被多個線程刪除的情況。
比如下面的代碼:
|
for循環構造10個線程刪除同一個集合中的數據,理論上只能刪除100000次。但是運行完發現,輸出的刪除次數108494次,其中很多數據都是被多個線程刪除,比如下面的輸出片段:
17ticket NO,35721
14ticket NO,35699
11ticket NO,35721
18ticket NO,35721
17ticket NO,35729
11ticket NO,35729
14ticket NO,35729
17ticket NO,35729
14ticket NO,35734
17ticket NO,35734
13ticket NO,35721
可以看到35721,35729都被多個線程刪除。這事實上就是出現了臟讀。解決的辦法就是加鎖,使得同一時刻只有1個線程對ArrayList做操作。
修改代碼,synchronized關鍵字,讓得到鎖對象的線程才能運行,這樣確保同一時刻只有一個線程操作集合。
|
這樣得到的結果就是準確的了。
當然,不使用synchronized關鍵字,而直接使用vector或者Collections.synchronizedList 也是同樣效果:
|
vector和Collections.synchronizedList 都是線程同步的,避免的臟讀的出現。