1. 程式人生 > >java.util.concurrent包中執行緒安全的集合簡介

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讀寫優化策略和分段加鎖策略,來提高併發性。在大量多執行緒的場景中選擇它們是比較合適的(線上程不多的時候未必能體現時間上的優勢)。