转载请注明出处:jiq•钦's technical Blog - 季义钦


1. 引言

多线程环境正确发布共享数据的方法之一就是线程安全容器。

线程安全的容器是由锁保护的域,将数据放入线程安全的容器中,可以保障其被安全地发布给所有从这个容器访问它的线程。


2. 同步容器类

JDK1.0开始有两个很老的同步容器类:Vector和HashTable。

JDK1.2之后Collections工具类中添加了一些工厂方法返回类似的同步封装器类:

public static <T> Collection<T>synchronizedCollection(Collection<T> c)

static<T> List<T> synchronizedList(List<T> list) //包装ArrayListLinkedList

static<T> Set<T> synchronizedSet(Set<T> s)  //包装HashSet

static<K,V> Map<K,V> synchronizedMap(Map<K,V> m) //包装HashMap

static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T>s) //包装TreeSet

static <K,V> SortedMap<K,V>synchronizedSortedMap(SortedMap<K,V> m) //包装TreeMap

 

实现方式:

将它们的状态封装起来,并对每一个公有方法进行同步。

其中Vector就是Object[]+synchronized方法,Hashtable是HashtableEntry[]+synchronized方法。而synchronizedXXX()方法返回的同步封装器类更是简单地将传进来的Collection的所有方法封装为synchronized方法而已。

 

缺点:

1、  通过同步方法将访问操作串行化,导致并发环境下效率低下

2、  复合操作(迭代、条件运算如没有则添加等)非线程安全,需要客户端代码来实现加锁。


3. 并发容器类

并发容器出现的最大的需求就是提升同步容器类的性能!

可以对比(非并发容器类)看看,将单线程版本和并发版本做一个比较。


1、HashMap和HashSet的并发版本

1.1 ConcurrentHashMap<K, V>HashMap的并发版本)

版本:JDK5

目标:代替Hashtable、synchronizedMap,支持复合操作

原理:采用一种更加细粒度的加锁机制“分段锁”,任意数量读取线程可以并发读取,任意数量的读取线程和一个写线程可以并发访问,一定数量的写入线程可以并发访问。并发环境下ConcurrentHashMap带来了更高的吞吐量,而在单线程环境下只损失了很小的性能。

 

1.2 CopyOnWriteArraySet<E>HashSet的并发版本)

版本:JDK5

目标:代替synchronizedSet

原理:CopyOnWriteArraySet基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法,其遍历当前Object数组,如Object数组中已有了当前元素,则直接返回,如果没有则放入Object数组的尾部,并返回。


2、TreeMap和TreeSet的并发版本

2.1 ConcurrentSkipListMap<K, V>TreeMap的并发版本)

版本:JDK6

目标:代替synchronizedSortedMap(TreeMap)

原理:Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过"空间来换取时间"的一个算法。ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上能够在O(log(n))时间内完成查找、插入、删除操作。

 

2.2 ConcurrentSkipListSet<E>TreeSet的并发版本)

版本:JDK6

目标:代替synchronizedSortedSet

原理:内部基于ConcurrentSkipListMap实现!

 

3、ArrayList和LinkedList的并发版本

3.1 CopyOnWriteArrayList<E>ArrayList的并发版本)

目标:代替Vector、synchronizedList

原理:CopyOnWriteArrayList的核心思想是利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。

 

3.2 ConcurrentLinkedQueue<E>(LinkedList的并发版本)

目标:代替Vector、synchronizedList

特点:基于链表实现的FIFO队列,特别注意单线程环境中LinkedList除了可以用作链表,也可用作队列,并发版本也一样

        

4、阻塞队列:BlockingQueue

版本:JDK1.5

特点:拓展了Queue,增加了可阻塞的插入和获取等操作

实现类

LinkedBlockingQueue:基于链表实现的可阻塞的FIFO队列

ArrayBlockingQueue:基于数组实现的可阻塞的FIFO队列

PriorityBlockingQueue:按优先级排序的队列

原理:通过ReentrantLock实现线程安全,通过Condition实现阻塞和唤醒。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐