JUC之各种线程安全容器

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值