java.util.concurrent包中執行緒安全的集合簡介
一、執行緒安全的集合
Java中有很多支援多執行緒併發的集合,比如Hashtable、Vector但是這些“古老”的併發集合效率並不高,一般只支援一個執行緒對其進行讀寫(加鎖是針對整張表)。從Java 5開始 Java在java.util.concurrent包中提供了更多,效率更高的執行緒安全集合。下面用一張圖片來顯示這些集合的繼承實現關係。
注:java也可以使用collections工具類的方法獲取一個執行緒安全的集合。
如上圖所示,Java考慮的非常周全,分別為Map、List、Queue、Set四種類型提供了新的執行緒安全集合類。下面將重點介紹其中的ConcurrentHashMap、CopyOnWriteArrayList,因為這兩者代表了兩類併發實現思想,介紹了它們的特性,其他的也就自然清楚了。
二、CopyOnWriteArrayList簡介
CopyOnWriteArrayList顧名思義就是在寫操作之前先複製一份,這樣讀操作不用加鎖,寫操作在複製的集合上修改,然後將新集合賦值給舊的引用,並通過volatile 保證其可見性,當然寫操作的鎖是必不可少的了。
正式因為這種利用空間換時間的思想,CopyOnWriteArrayList需要注意記憶體佔用問題,比較好的實踐是,儘量在新增的時候做批量新增。
三、ConcurrentHashMap簡介
ConcurrentHashMap本質上還是一個HashMap,它支援多個執行緒同時對它進行讀寫操作。在預設情況下,ConcurrentHashMap支援16個執行緒同時進行讀寫操作。那麼它實現這種特性的原理是什麼呢?
(1)它在並沒有在讀操作上加鎖,只是在寫操作上進行了加鎖——volatile應用和happen-before原則。
(2)分段加鎖:ConcurrentHashMap把自己分成多個段(segment),每個段其實是一個小HashTable,如果多個執行緒訪問的段不同,那麼就可以進行併發操作,其實主要思想就是加鎖的粒度更小了而已。
(3)對於需要對整個集合加鎖的方法,比如size(),它會按順序加鎖,操作完畢後,再按加鎖順序釋放鎖。
下面用程式碼來比較一下使用Hashtable和ConcurrentHashMap的併發效率,分別用1萬個執行緒對它們執行寫操作,通過對任務的完成時間來進行比較併發效能。
package com;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws Exception {
//比較一下HashTable和ConcurrentHashMap的效率
final Hashtable<Integer,String> h_table=new Hashtable<>();
final ConcurrentHashMap<Integer,String> conMap=new ConcurrentHashMap<>();
//開啟2W個執行緒,每個集合1w個執行緒進行寫入操作,計算時間效能
//記錄開始時間
Calendar start_time=Calendar.getInstance();
start_time.setTime(new Date());
//開啟一個執行緒池1W個執行緒往Hashtable中寫資料
ExecutorService pool1=Executors.newCachedThreadPool();
for(int i=1;i<=10000;i++){
final int j=i;
pool1.submit(new Runnable(){
public void run(){
h_table.put(j, "任務:"+j);
}
});
}
pool1.shutdown();
while(!pool1.isTerminated()){
//空迴圈等待執行緒池中的執行緒執行完成
}
//記錄完成時間,打印出時間差
Calendar f_time1=Calendar.getInstance();
f_time1.setTime(new Date());
System.out.println("Hashtable上讀寫耗時:"+ (f_time1.getTimeInMillis()-start_time.getTimeInMillis()));
//開啟一個執行緒池1W個執行緒往ConcurrentHashMap中寫資料
ExecutorService pool2=Executors.newCachedThreadPool();
for(int i=1;i<=10000;i++){
final int j=i;
pool2.submit(new Runnable(){
public void run(){
conMap.put(j, "任務:"+j);
}
});
}
pool2.shutdown();
while(!pool2.isTerminated()){
//空迴圈等待執行緒池中的執行緒執行完成
}
//記錄完成時間,打印出時間差
Calendar f_time2=Calendar.getInstance();
f_time2.setTime(new Date());
System.out.println("ConCurrentHashMap上讀寫耗時:"+ (f_time2.getTimeInMillis()-f_time1.getTimeInMillis()));
}
}
結果如下:
Hashtable上讀寫耗時:250
ConCurrentHashMap上讀寫耗時:70
四、總結
java.util.concurrent包提供的執行緒安全類,主要是利用copy-on-write讀寫優化策略和分段加鎖策略,來提高併發性。在大量多執行緒的場景中選擇它們是比較合適的(線上程不多的時候未必能體現時間上的優勢)。