一、JUC简介
Java 5.0 提供了java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常见的实用工具类,用于定义类似于编程的自定义子系统,包括线程池、异步IO和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文的Collection实现等。
二、HashMap 和 Hashtable 的问题
HashMap 线程不安全;Hashtable因为使用了synchronized修饰方法而导致执行效率不高。
三、ConcurrentHashMap
- Java5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
- ConcurrentHashMap 同步容器类是 Java 5 增加的一个线程安全的哈希表。对于多线程的操作,介于 HashMap 与Hashtable之间。内部采用“锁分段”机制替代Hashtable的独占锁,进而提高性能。
- 此包中还提供了设计用于多线程上下文中的Collection实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给定collection时,ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常由于同步的TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList。
四、ConcurrentHashMap的实现
JDK1.7及之前的 ConcurrentHashMap 使用 锁分段机制 实现,JDK1.8则使用 数组+链表+红黑树数据结构 和 CAS原子操作实现。
- JDK 1.5 - 1.7
Hashtable 之所以效率低下,主要是因为其实现使用了synchronized 关键字对 put 等操作进行加锁,而 synchronized 关键字加锁是对整个对象加锁,也就是说在进行put等修改Hashtable的操作时,锁住了整个Hash表,从而使得其效率低下。因此在JDK1.5~1.7版本,Java使用分段锁机制实现ConcurrentHashMap。锁分段机制的ConcurrentHashMap 模拟图如下:
ConcurrentHashMap找那个定义了一个Segment<K, V>[]数组来讲Hash表分段存储,从而实现分段加锁;而每个Segment元素则与HashMap结构类似,其包含了一个HashEntry数组,用来存储Key/Value对。Segment继承了ReetrantLock,表示Segment是一个可重入锁,因此ConcurrentHashMap通过可重入锁对每个分段进行加锁。 - JDK 1.8
JDK 1.8之前,ConcurrentHashMap采用锁分段机制,其最大并发度受Segment的个数限制。因此JDK 1.8 摒弃了这种设计,而是选择了与HashMap类似的数组+链表+红黑树的方式实现,而加锁则采用CAS + synchronized 实现。
关于CAS的说明,可简单参考 JUC入门系列(二)-CAS算法
JDK 1.8 的ConcurrentHashMap的数据结构比JDK 1.7 之前的要简单的多,其使用的是HashMap一样的数据结构:数组+链表+红黑树。ConcurrentHashMap 中包含一个table数组,其类型是一个Node数组;而Node数组是一个继承自Map.Entry<K,V> 的链表,而当这个链表结构中的数组大于8,则将数据结构升级为TreeBin类型的红黑树结构。另外,JDK 1.8 的ConcurrentHashMap中还包含一个重要的属性,sizeCtl,它是一个控制标识符,不同的值代表不同的意思:为0时,表示hash还没初始化;为正数时表示初始化或下一次扩容的大小,相当于一个阈值。即:如果hash表的实际大小 >= sizeCtl,则进行扩容,默认情况下为当前ConcurrentHashMap容量的0.75倍;而如果sizeCtl 为 -1,表示正在进行初始化操作。为 -N 时,则表示有 N-1 个线程正在进行扩容。
(以上部分内容参考:Java并发包concurrent——ConcurrentHashMap)