CopyOnWrite系列、Concurrent系列
1.0大体结构
Java提供了不同层面的线程安全支持。在传统集合框架内部,除了HashTable等同步容器,还提供了所谓的同步包装器(Synchronized Wrapper),我们可以调用Collections 工具类提供的包装方法,来获取一个同步的包装容器(如Collections.synchronizedMap),但是他们都是非常粗粒度的同步方式,在高并发下,性能比较低下。
更加普遍的做法是选择利用并发包提供的线程安全容器类,它提供了:
- 各种并发容器 :ConcurrentHashMap、CopyOnWriteArrayList
- 各种线程安全队列(Queue/Deque),ArrayBlockingQueue、SynchronousQueue。
- 各种有序容器的线程安全版本。
总体结构比较简单。
2.0 Concurrent系列
2.1 为什么需要ConcurrentHashMap
HashTable比较低效,因为它的实现就是将put、get、size等方法加上“Synchronized”,这就导致了所有并发操作都要竞争同一把锁,一个线程在进行同步操作时其他线程只能等待,这大大降低了并发操作的效率。那么能不能用上面提到的Collections提供的同步包装器来解决问题呢?
请看下面代码,我们发现同步包装器只利用输入Map构造了另一个同步版本,所有操作虽然不再声明为Synchronized方法,但是还是利用了this作为互斥的mutex(互斥量,名词),没有真正意义上的改进。
private static class SynchronizedMap<K, V> implements Map<K, V>, Serializable {
private final Map<K, V> m; // Backing Map
final Object Mutex; // Object on which to Synchronzie
// ...
public int size() {
synchronized(mutex) {
return m.size();}
}
// ...
}
结论:所以HashTable 或者同步包装版本,非高度并发的场景下
2.2 JDK1.7 ConcurrentHashMap分析
早期ConcurrentHashMap的实现是基于:
- 分离锁,也就是将内部进行分段(Segment),里面则是HashEntry的数组,和HashMap类似,哈希相同的条目也是以链表形式存放。
- HashEntry内部使用Volatile的value字段来保证可见性,也利用了不可变的机制来改进利用Unsafe提供的底层能力,比如volatile Access,去直接完成部分操作,以最优化性能,毕竟Unsafe中的很多操作都是JVM intrinsic(固有的、内在的、本身的,形容词)优化过的。
总结: 早期的ConcurrentHashMap 其核心是利用分段设计,在进行并发操作时,只需要锁定相应的段,这样就有效避免了类似Hashable 整体同步的问题,大大提高了性能。
在构造时,segment的数量由所谓的concurrentcyLevel决定,默认是16,也可以在相应构造函数直接指定。注意: Java需要它是2的幂次方,所以如果是15之类的值会自动调整到16之类的,变成2的幂次方。
2.2.1 JDK1.7 ConcurrentHashMap的get()源码分析
get操作需要保证的是可见性,所以并没有什么同步逻辑。
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
int h = hash(key.hashCode());
//利用位操作替换普通数学运算
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
// 以Segment为单位,进行定位
// 利用Unsafe直接进行volatile access
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
//省略
}
return null;
}
2.2.2 JDK1.7 ConcurrentHashMap的 put()源码分析
而对于put操作,首先通过二次哈希,避免hash冲突,然后以Unsafe调用方式,直接获取相应的Segment,然后进行线程安全的put操作。
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
thro