2011年11月23日

Java:ArrayList如何達成執行緒安全(Thread Safe)


 
Java 的容器類別(Collection)非常好用,
常用的Collection 有 ArrayList, LinkedList, Map, HashMap, Set, HashSet等,
然而這些類別在設計時並沒有加入執行緒安全(Thread Safe)的功能,
所以如果要將這些的好用的類別用在多執行緒的程式環境中就必須自己實作相關的程式,
來確保 Collection 中共用的資料在多執行緒執行時存取不錯出錯。

要達成執行緒安全(Thread Safe)最簡單的方法就是把共用的物件同步化(synchronized),
這個方法也常被用於鎖定其他非 Collection 類別物件來保護共用的資料。
使用同步化(synchronized)鎖定 arrayList 的方式如下:
  1 ArrayList arrayList = new ArrayList();
  2 Object object = new Object();
  3 
  4 synchronized(arrayList) {
  5     arrayList.add(object); 
  6     arrayList.remove(object); 
  7 } 
然而由於類似的需求很大, Java 也一直在進步,
所以後來 Collections 中也出現了某些 API 可以傳回同步化的 Collection 物件,如:
  1 Collections.synchronizedCollection(collection);
  2 Collections.synchronizedList(list);
  3 Collections.synchronizedMap(map);
  4 Collections.synchronizedSet(set);
  5 Collections.synchronizedSortedMap(sortedMap);
  6 Collections.synchronizedSortedSet(sortedSet);
以剛剛的例子,若我們要取得同步化的 ArrayList,就可以使用以下的程式:
  1 List arrayList = Collections.synchronizedList(new 
  2                     ArrayList());
  3 arrayList.add("http://werdna1222coldcodes.blogspot.com/");
  4 arrayList.add("符碼記憶");
要注意的是,以Collections.synchronizedList(new ArrayList()); 傳回的物件,
雖然在存取資料時會進行同步化,所以存取資料並不會出錯,
但若要使用 Iterator 遍訪 List 物件或其他 Collection 時並不會同步化,
主要原因是因為用這種方法得到的 Collection 呼叫 iterator()方法傳回的Iterator物件,
並不會保證執行緒安全(Thread Safe),所以得像一開始的例子一樣用synchronized。
  1 List arrayList = Collections.synchronizedList(new 
  2                     ArrayList());
  3 arrayList.add("http://werdna1222coldcodes.blogspot.com/");
  4 arrayList.add("符碼記憶");
  5 
  6 synchronized(arrayList) {
  7     Iterator iterator = arrayList.iterator(); 
  8     while (iterator.hasNext()){
  9         System.out.println(iterator.next());
 10     }
 11 }
除了以上的方法,在J2SE 5.0之後 Java 又更進一步新增了 java.util.concurrent,
這個 package 中加入了一些可以確保執行緒安全的 Collection 類別,
例如 ConcurrentHashMap、ConcurrentLinkedQueue等以 Concurrent 為開頭的類別,
另外也有 CopyOnWriteArrayList、CopyOnWriteArraySet 等以 CopyOnWrite 為開頭的類別,
這些新增的 Collection 類別分別實作或繼承了 Map、List、Set 介面,
所以基本的功能都是一樣的,差別只在新增了同步化的功能。
除此之外,這些類別也額外針對效率和安全性間的取捨做了一些設定,
依據不同物件的特性會有不同的同步化實作,以確保效率與安全性。
以下文字節錄自 Java Gossip: 容器類的執行緒安全(Thread-safe)
例如ConcurrentHashMap,它針對Hash Table中不同的區段(segment)進行同步化,而不是對整個物件進行同步化,預設上HashMap有16個區段,當有執行緒在存取第一個區段時, 第一個區域進入同步化,然而另一個執行緒仍可以存取第一個區段以外的區段,而不用等待第一個執行緒存取完成,所以與同步化整個物件來說,新增的這些同步化物件,在效率與安全性上取得了較好的平衡。

看完了以上的說明,相信大家已經知道同步化可以如何達成,
若在需要同步化的情況下,直接利用最新的 java.util.concurrent package 似乎比較簡單,
且又可以兼顧效能及安全性,相信這是首選。
除了這些新增同步化功能的 Collection 類別外,Java 在很久以前 Vector 就已支援同步化,
下面這一篇針對 ArrayList 和 Vector 的適用情況做了很清楚的說明,
若不介意看英文,相信會從其間得到許多:
Vector or ArrayList -- which is better?

關鍵字:java, List, Map, Set, ArrayList, LinkedList, HashMap, HashSet, Thread, Safe, Synchronize, Synchronized, 同步, 非同步, 同步化, 執行緒, 安全, 效率, 效能, 比較
參考資料:


更多精選推薦文章