Java集合類中,某個線程在 Collection 上進行迭代時,通常不允許另一個線性修改該 Collection.通常在這些情況下,迭代的結果是不確定的.如果檢測到這種行為,一些迭代器實現(包括 JRE 提供的所有通用 collection 實現)可能選擇拋出此異常.執行該操作的迭代器稱為快速失敗 迭代器,因為迭代器很快就完全失敗,而不會冒著在將來某個時間任意發生不確定行為的風險.
因此,當一個線程試圖ArrayList的數據的時候,另一個線程對ArrayList在進行迭代的,會出錯,拋出ConcurrentModificationException.
比如下面的代碼:
1.final List<String> tickets = new ArrayList<String>();
2.for (int i = 0; i < 100000; i ) {
3. tickets.add("ticket NO," i);
4.}
5.System.out.println("start1...");
6.for (int i = 0; i < 10; i ) {
7. Thread salethread = new Thread() {
8. public void run() {
9. while (tickets.size() > 0) {
10. tickets.remove(0);
11. System.out.println(Thread.currentThread().getId() "Remove 0");
12. }
13. }
14. };
15. salethread.start();
16.}
17.System.out.println("start2...");
18.new Thread() {
19. public void run() {
20. for (String s : tickets) {
21.System.out.println(s);
22. }
23. }
24.}.start();
上述程序運行后,會在某處拋出異常:
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個線程從該集合中刪除元素,那麼每個元素只可能由一個線程刪除掉,不可能會出現一個元素被多個線程刪除的情況.
比如下面的代碼:
1.final List<String> tickets = new ArrayList<String>();
2.for (int i = 0; i < 100000; i ) {
3. tickets.add("ticket NO," i);
4.}
5.System.out.println("start1...");
6.for (int i = 0; i < 10; i ) {
7. Thread salethread = new Thread() {
8. public void run() {
9. while (true) {
10.if(tickets.size()>0)
11.System.out.println(Thread.currentThread().getId() tickets.remove(0));
12.else
13.break;
14. }
15. }
16. };
17. salethread.start();
18.}
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關鍵字,讓得到鎖對象的線程才能運行,這樣確保同一時刻只有一個線程操作集合.
1.final List<String> tickets = new ArrayList<String>();
2.for (int i = 0; i < 100000; i ) {
3. tickets.add("ticket NO," i);
4.}
5.System.out.println("start1...");
6.final Object lock=new Object();
7.for (int i = 0; i < 10; i ) {
8. Thread salethread = new Thread() {
9. public void run() {
10. while (true) {
11. synchronized(lock)
12. {
13. if(tickets.size()>0)
14. System.out.println(Thread.currentThread().getId() tickets.remove(0));
15. else
16. break;
17. }
18. }
19. }
20. };
21. salethread.start();
22.}
這樣得到的結果就是準確的了.
當然,不使用synchronized關鍵字,而直接使用vector或者Collections.synchronizedList 也是同樣效果:
1.final List<String> tickets =java.util.Collections.synchronizedList(new ArrayList<String>());
2.final List<String> tickets =new Vector<String>();
vector和Collections.synchronizedList 都是線程同步的,避免的臟讀的出現.
[火星人 ] 多線程中使用Java集合類已經有468次圍觀