ConcurrentHashMap(简称CHM
)是在Java 1.5作为Hashtable
的替代选择新引入的,是concurrent包的重要成员
在Java 1.5之前,如果想要实现一个可以在多线程和并发的程序中安全使用的Map,只能在HashTable和synchronized Map中选择
- 因为HashMap并不是线程安全的
- ConcurrentHashMap不但是线程安全的,而且比HashTable和synchronizedMap的性能要好
- 根据默认的并发级别(
concurrency level
),Map被分割成16个部分,并且由不同的锁控制- 这意味着,同时最多可以有16个写线程操作Map
- 试想一下,由只能一个线程进入变成同时可由16个写线程同时进入(读线程几乎不受限制),性能的提升是显而易见的
- 在迭代遍历CHM时,keySet返回的iterator是弱一致和fail-safe的,可能不会返回某些最近的改变
- 并且在遍历过程中,如果已经遍历的数组上的内容变化了,不会抛出ConcurrentModificationExceptoin的异常
很多时候我们希望在元素不存在时插入元素,我们一般会像下面那样写代码
- 上面这段代码在HashMap和HashTable中是好用的,但在CHM中是有出错的风险的
- CHM的比HashTable的同步性稍弱
- 这是因为CHM在put操作时并没有对整个Map加锁,所以一个线程正在put(k,v)的时候
- 另一个线程调用get(k)会得到null,这就会造成一个线程put的值会被另一个线程put的值所覆盖
- 这就是对segment 加锁的原因
- 上面的map 仅仅是锁对象,不代表别的线程不能使用map ,仅仅代表别的线程不能进入该代码块
- CHM提供的putIfAbsent(key,value)方法原子性的实现了同样的功能,同时避免了上面的线程竞争的风险
总结
- CHM允许并发的读和线程安全的更新操作
- 在执行写操作时,CHM只锁住部分的Map
- 并发的更新是通过内部根据并发级别将Map分割成小部分实现的
- 高的并发级别会造成时间和空间的浪费,低的并发级别在写线程多时会引起线程间的竞争
- CHM的所有操作都是线程安全
- CHM返回的迭代器是弱一致性,fail-safe并且不会抛出ConcurrentModificationException异常
- 因为每次更新内容时,仅仅是锁一个segment,不是整个map锁定
- CHM不允许null的键值
- 可以使用CHM代替HashTable,但要记住CHM不会锁住整个Map
从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念
- 具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中
- 就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中