JDK5中添加了新的concurrent包,相对同步容器而言,并发容器经过一些机制改进了并发性能。由于同步容器将全部对容器状态的访问都串行化了,这样保证了线程的安全性,因此这种方法的代价就是严重下降了并发性,当多个线程竞争容器时,吞吐量严重下降。所以Java5开始针对多线程并发访问设计,提供了并发性能较好的并发容器,引入了java.util.concurrent包。java
与Vector、HashTable、Collections.synchronizedXxx()等同步容器相比,util.concurrent中引入的并发容器主要解决了两个问题:编程
util.concurrent中容器在迭代时,能够不封装在synchronized中,能够保证不抛异常,可是未必每次看到的都是"最新的、当前的"数据。数组
下面是对并发容器的简单介绍:安全
ConcurrentHashMap代替同步的Map(Collections.synchronized(new HashMap())),众所周知,HashMap是根据散列值分段存储的,同步Map在同步的时候锁住了全部的段,而ConcurrentHashMap加锁的时候根据散列值锁住了散列值锁对应的那段,所以提升了并发性能。ConcurrentHashMap也增长了对经常使用复合操做的支持,好比"若没有则添加":putIfAbsent(),替换:replace()。这2个操做都是原子操做。数据结构
CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set,主要是在遍历操做为主的状况下来代替同步的List和同步的Set,这也就是上面所述的思路:迭代过程要保证不出错,除了加锁,另一种方法就是"克隆"容器对象。多线程
ConcurrentLinkedQueue是一个先进先出的队列。它是非阻塞队列。并发
ConcurrentSkipListMap能够在高效并发中替代SortedMap(例如用Collections.synchronizedMap包装的TreeMap)。性能
ConcurrentSkipListSet能够在高效并发中替代SortedSet(例如用Collections.synchronizedSet包装的TreeMap)。线程
并发编程中使用HashMap可能致使程序死循环,致使cpu利用率接近100%。具体缘由是,再执行put操做时会引发死循环,多线程会致使HashMap的Entry链表造成环形数据结构,这样的话Entry的next节点永远不为空,就会产生死循环获取Entry。设计
HashTable使用synchronized来保证线程安全,竞争激烈状况下,当一个线程访问同步方法,其余线程也访问同步方法,会进入阻塞或轮询状态。线程1使用put进行元素添加,线程2不但不能使用put方法,也不能使用get方法。
ConcurrentHashMap将数据分红一段一段地存储,而后给每一段数据配一把锁,而且其内部的结构可让其在进行写操做的时候可以将锁的粒度保持尽可能的小,不用对整个ConcurrentHashMap加锁。当一个线程占用锁访问其中一个段数据的时候,其余段地数据也能被其余线程访问。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap相似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每一个HashEntry是一个链表结构的元素, 每一个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。
ConcurrentHashMap和HashTable的区别图
从上面的结构咱们能够了解到,ConcurrentHashMap定位一个元素的过程须要进行两次Hash操做,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,所以,这一种结构的带来的反作用是Hash的过程要比普通的HashMap要长,可是带来的好处是写操做的时候能够只对元素所在的Segment进行加锁便可,不会影响到其余的Segment,这样,在最理想的状况下,ConcurrentHashMap能够最高同时支持Segment数量大小的写操做(恰好这些写操做都很是平均地分布在全部的Segment上),因此,经过这一种结构,ConcurrentHashMap的并发能力能够大大的提升。