摘要:是线程安全,性能出色的的线程安全实现,相比较他是线程安全的,相比较他的性能优势非常明显。的源码其实很简单,和的结构一致,但是每一个方法都是用来修饰,以保证操作是线程安全的。这样在多线程的情况下,只有一个线程获取锁操作中的数据。
ConcurrentHashMap
ConcurrentHashMap是线程安全,性能出色的Map的线程安全实现,相比较HashMap他是线程安全的,相比较HashTable他的性能优势非常明显。他的使用很简单,这里主要是想要探究一下ConcurrentHashMap的实现原理。
在这里一共有 个问题需要搞明白。
ConcurrentHashMap为什么比HashTable的性能要高?
ConcurrentHashMap在JDK8和JDK7有什么变化,为什么会有这种变化,对我们开发有什么启示?
为什么在JDK8中使用Synchronized而不是用ReentrantLock来实现加锁?
带着这几个问题我们来分析一下ConcurrentHashMap的源码吧。
在JDK8(JDK7也是一样)中ConcurrentHashMap的定义如下:
public class ConcurrentHashMapextends AbstractMap implements ConcurrentMap , Serializable {
Java7中ConcurrentHashMap的实现是基于分段锁实现的。他的底层数据结构仍然是数组+链表,与HashTable不同的是,ConcurrentHashMap的最外层不是一个大的数组,而是一个Segment数组(分段锁的实现)。分段锁减小了锁的粒度,提高了并发程度。这也是为什么比HashTable效率要高的原因。
HashTable的源码其实很简单,HashTable和HashMap的结构一致,但是每一个方法都是用Synchronized来修饰,以保证操作是线程安全的。这样在多线程的情况下,只有一个线程获取锁操作hashTable中的数据。而CourrentHashMap则不是,它允许最多有segment数组长度个线程同时操作ConcurrentHashMap中的数据。
ConcurrentHashMap的整体结构如下(图片来源:http://www.jasongj.com/java/c...):
ConcurrentHashMap的定义:
public class ConcurrentHashMapextends AbstractMap implements ConcurrentMap , Serializable { private static final long serialVersionUID = 7249069246763182397L; /** * 表的默认容量 */ static final int DEFAULT_INITIAL_CAPACITY = 16; /** * 默认扩容因子 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * segments数组的默认长度,为了能通过按位与的散列算法来定位segments数组的索引,必须保证segments数组的长度是2的N次方 */ static final int DEFAULT_CONCURRENCY_LEVEL = 16; /** * HashEntry最大容量 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * segment的最小容量 */ static final int MIN_SEGMENT_TABLE_CAPACITY = 2; /** * segment的最大容量 */ static final int MAX_SEGMENTS = 1 << 16; // slightly conservative /** * 重试次数,无锁的情况下尝试两次 */ static final int RETRIES_BEFORE_LOCK = 2; /** * 散列运算的掩码,等于ssize-1 */ final int segmentMask; /** * 定位参与散列运算的位数,等于32-sshift */ final int segmentShift; /** * 定义segment数组 */ final Segment [] segments;
Segment定义:
static final class Segmentextends ReentrantLock implements Serializable { transient volatile HashEntry [] table; transient int count; transient int modCount; //扩容量,默认为表的容量*加载因子,实际量超过这个值的时候,进行扩容 transient int threshold; //segment中hashEntry的扩容因子 final float loadFactor; }
get()操作经过两次散列找到HashEntry,然后进行遍历的操作,get方法使用的变量都是volatile类型的,可以保证线程可见,能够被多线程同时读,并且保证不会读取到过期的值,在get操作中需要读count和value两个共享变量,所以不需要加锁,volatile字段的写入操作会先于读操作,所有即使有一个线程在修改,get也能获取到最新的值。
put() 先对变量的hashCode进行一次再散列然后定位到Segment,然后再Segment中进行插入操作。如果HashEntry数组超过threshold,那么扩容,扩容只是针对Segment进行扩容。
size() 统计ConcurrentHashMap中元素的个数,需要对Segment中所有元素进行求和,Segment里全局变量count是一个volatile类型的变量,累加可以获取元素的总个数,但是不一定准确,因为使用过的count再后面可以改变,最后的方法就是阻塞put,remove,clean等元素操作的方法,但是这样非常低效。所以Concurrenthashmap通过尝试两次不锁来统计segment的元素大小,如果两次结果不一样,那么使用加锁的方式来统计,容器是否变化是通过modCount是否变化来确定的。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/77795.html
摘要:中的使用及在中的冲突方案引言简称是在作为的替代选择新引入的,是包的重要成员。为了解决在频繁冲突时性能降低的问题,中使用平衡树来替代链表存储冲突的元素。目前,只有和会在频繁冲突的情况下使用平衡树。 java中ConcurrentHashMap的使用及在Java 8中的冲突方案 1、引言 ConcurrentHashMap(简称CHM)是在Java 1.5作为Hashtable的替代选择新...
摘要:在中一般来说通过来创建所需要的线程池,如高并发原理初探后端掘金阅前热身为了更加形象的说明同步异步阻塞非阻塞,我们以小明去买奶茶为例。 AbstractQueuedSynchronizer 超详细原理解析 - 后端 - 掘金今天我们来研究学习一下AbstractQueuedSynchronizer类的相关原理,java.util.concurrent包中很多类都依赖于这个类所提供的队列式...
摘要:在中一般来说通过来创建所需要的线程池,如高并发原理初探后端掘金阅前热身为了更加形象的说明同步异步阻塞非阻塞,我们以小明去买奶茶为例。 AbstractQueuedSynchronizer 超详细原理解析 - 后端 - 掘金今天我们来研究学习一下AbstractQueuedSynchronizer类的相关原理,java.util.concurrent包中很多类都依赖于这个类所提供的队列式...
摘要:表示的是两个,当其中任意一个计算完并发编程之是线程安全并且高效的,在并发编程中经常可见它的使用,在开始分析它的高并发实现机制前,先讲讲废话,看看它是如何被引入的。电商秒杀和抢购,是两个比较典型的互联网高并发场景。 干货:深度剖析分布式搜索引擎设计 分布式,高可用,和机器学习一样,最近几年被提及得最多的名词,听名字多牛逼,来,我们一步一步来击破前两个名词,今天我们首先来说说分布式。 探究...
阅读 2730·2021-11-16 11:44
阅读 943·2021-10-09 09:58
阅读 4441·2021-09-24 09:48
阅读 4000·2021-09-23 11:56
阅读 2379·2021-09-22 15:48
阅读 1844·2021-09-07 10:07
阅读 3162·2021-08-31 09:46
阅读 476·2019-08-30 15:56