Java 并发---ConcurrentHashMap
concurrent包下的并发容器
JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能。因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全性,所以这种方法的代价就是严重降低了并发性,当多个线程竞争容器时,吞吐量严重降低。因此Java5开始针对多线程并发访问设计,提供了并发性能较好的并发容器,引入了java.util.concurrent包。
与Vector、HashTable、Collections.synchronizedXxx()等同步容器相比,util.concurrent中引入的并发容器主要解决了两个问题:
- 根据具体场景进行设计,尽量避免synchronized,提供并发性。
- 定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。
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)。
为什么使用ConcurrentHashMap
线程不安全的HashMap
并发编程中使用HashMap可能导致程序死循环,导致cpu利用率接近100%。具体原因是,再执行put操作时会引起死循环,多线程会导致HashMap的Entry链表形成环形数据结构,这样的话Entry的next节点永远不为空,就会产生死循环获取Entry。
效率低下的HashTable
HashTable使用synchronized来保证线程安全,竞争激烈情况下,当一个线程访问同步方法,其他线程也访问同步方法,会进入阻塞或轮询状态。线程1使用put进行元素添加,线程2不但不能使用put方法,也不能使用get方法。
ConcurrentHashMap使用锁分段技术
ConcurrentHashMap将数据分成一段一段地存储,然后给每一段数据配一把锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持尽量的小,不用对整个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的并发能力可以大大的提高。