java.util.concurrent包中线程安全的集合简介

一、线程安全的集合

       Java中有很多支持多线程并发的集合,比如Hashtable、Vector但是这些“古老”的并发集合效率并不高,一般只支持一个线程对其进行读写(加锁是针对整张表)。从Java 5开始 Java在java.util.concurrent包中提供了更多,效率更高的线程安全集合。下面用一张图片来显示这些集合的继承实现关系。

      注:java也可以使用collections工具类的方法获取一个线程安全的集合。



java.util.concurrent包中线程安全的集合简介_第1张图片




如上图所示,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 h_table=new Hashtable<>();
		final ConcurrentHashMap 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读写优化策略和分段加锁策略,来提高并发性。在大量多线程的场景中选择它们是比较合适的(在线程不多的时候未必能体现时间上的优势)。


     

你可能感兴趣的:(JAVA)