目录
线程并发问题介绍
字典类是JAVA编程中使用很频繁的类,如HashMap,TreeMap等。不过这些类不是线程安全的,如果多线程共享同一个实例对象,在进行并发操作的时候可能会导致数据不一致或者线程死锁问题。例如HashMap内部是采用链表来解决哈希冲突的,当一个线程调用get方法获取数据,另一个线程调用put填充数据而且数据容量超过(加载因子*哈希表长度)的时候就会发生rehash扩容,链表结构发生改变,可能会导致get方法的线程发生死锁。
Hashtable
特点
- 同步版的HashMap(数据结构一样,内部实现有很多不同)
- 并发性能低,对每个方法使用synchronized关键字修饰
- 使用Hashtable对象实例自身作为监视器对象monitor (任何时候只有一个线程对实例进行访问)
劣势
- synchronized导致性能问题
- MashMap在内部实现更加高效,如根据key的哈希值与数组长度计算某个哈希值节点在哈希表的数组下标的实现,Hashtable是使用%取余,而HashMap是采用位运算
SynchronizedMap
简介
SynchronizedMap是包装器设计模式的一种运用,内部包装了一个Map接口实现类对象引用,通过引用对象来对数据进行实际读写操作。
特点
和Hashtable都是依赖Synchronize关键字进行线程同步,但是内部实现还是HashMap,上述已说明HashMap内部实现性能比Hashtable更好,所以SynchronizedMap更高效
劣势
- 并发性能低,对每个方法内部使用synchronized关键字修饰引用对象调用
用法
由于SynchronizedMap是集合工具类Collections的一个内部私有类,通过调用collections的静态方法synchronizedMap来对指定的Map对象进行包装
Map synchronizedMap = Collections.synchronizedMap(new HashMap());
ConcurrentHashMap
简介
底层实现跟HashMap类似
特点
- 和Hashtabel一样key和value都不循序是空值null
- 使用final修饰Hash和key(表示不可变),使用volatile修饰value和next指针(保证数据操作可见性)
- JDK8以前
- 内部使用链式哈希表来实现(Node节点组成的数组table)
- 使用多个哈希表,也称为多个分段锁实现(一个Segment相当于一个hashMap)
- 使用可重入锁ReentrantLock来进行线程同步
- Segments数组默认大小为16
- Segments数组以及内部数组大小都符合2的n次方。这个约束是因为要通过key的哈希值与数组大小进行位运算得出该键值对的存放位置
- 内部Segment大小由整体容量大小(最大为1<<30)除以Segment数组(也就是并发量,最大为1<<16)大小得到
- JDK8及以后
- 基于CAS机制和自旋基于JDK提供的UNSAFE类提供硬件级别的原子操作来实现无锁化
- 使用baseCount和counterCells:对JDK7的size进行优化。默认更新baseCount值,若出现并发更新(CAS更新失败)则在counterCells数组中添加一个counterCell。之后在调用size时,先累计加载counterCells,再与baseCount相加。
- synchronized关键字在更新链表节点或者在已经存在的链表中添加节点时,会以头结点为监视器对象进行加锁(也就是说只锁住当前链表,JDK8前是锁住Segment中的所有来链表)。进入同步代码块后,再次确认头结点是否被更改,避免进入同步代码块之前头结点被修改。
注意点
ConcurrentHashMap的线程安全主要集中体现在读写key的时候,不需要额外的加锁。但是对于值value,没有线程安全的保障,如果出现并发修改value(这里的value是除了String这种不可变类型或线程安全的原子性类型之外的类型,如线程不安全的Set集合和Integer),则需要额外加锁来实现线程同步
ConcurrentSkipListMap
简介
ConcurrentSkipListMap实现了一个并发,线程安全版本的TreeMap(实现了Map的键key的有序性)。
特点
- 实现了key的有序性
- 在数据结构方面使用了跳表(类似于树的链表),调表时间复杂度为O(lgN),而TreeMap使用红黑树,时间复杂度一样,故数据结构差不多
- 性能优于TreeMap结合锁操作
- 基于CAS机制和自旋基于JDK提供的UNSAFE类提供硬件级别的原子操作来实现无锁化,性能低于ConcurrentHashMap
- 可以通过interator和foreach进行遍历访问或操作
- iterator:迭代器基于快照实现,所以数是据弱一致性的,且在性能方面升序key迭代性能要优于降序key