ConcurrentHashMap源码

HashMap是一种应用频率非常高的Map,但它并不是线程安全的,在并发环境put会出现链表闭环,导致get时候无限循环,cpu升值100。Hashtable和Collections.synchronizedMaps是一种线程安全的Map,他们几乎对每个方法加synchronized来保证线程安全性,只能独占式读写,一次只允许一个线程访问读写方法,性能低下,吞吐量低。ConcurrentHashMap是一种高性能的线程安全的Map,它使用CAS+Synchronized保证线程安全性。

table 是节点数组,volatile确保可见性。

特殊hash值:大于等于0--链表   -1--扩容    <0--树

sizeCtl:默认值为0,可以用构造器改变其值。如果数组table还没有初始化,它的值表示数组初始化大小,没指定则为默认值16;-1代表有线程正在对数组进行初始化;-N 代表有N-1个线程正在扩容;其他正值代表扩容阈值,数组大小的0.75倍。

initTable()初始化数组:使用自旋+cas确保线程安全,执行过程:如果数组是空的,则自旋,如果sizeCtl<0,说明其他线程正在初始化数组,线程礼让,重新竞争cpu,否则 cas修改sizeCtl值为-1,失败重新自旋,成功则抢占到了初始化数组的权利,执行初始化数组,如果之前的值大于0则数组初始化大小为之前的值,否则初始化为默认值16,修改sizeCtl大小为数组大小的0.75倍作为扩容阈值,这个是直接修改的没用cas,因为只有一个线程可以执行,故而线程安全,最后结束返回。整体功能,线程安全的初始化数组,修改sizeCtl值为数组大小0.75倍作为扩容阈值。

transfer()扩容:扩容支持并发进行先到达的线程创建数组,后到达的多个线程一起拷贝元素,一个线程创建新数组,多个线程参与把原数组的元素移动到新数组。详细过程:第一个线程创建一个长度为原来2倍的的新数组;把原来的数组进行分组(cas保证,失败重新获取组,不同的线程获取不同的组),每组最少16个元素,由一个线程来处理,把每组的元素从组尾向前进行拷贝,操作完一组,再从原数组获取下一组,拷贝时候,会先对节点加synchronized锁,保证没有其他线程修改节点,如果元素是null,则设置特殊节点(hash值是1),链表的元素拷贝到新数组的原位置或者原位置+原数组长度,原数组的位置设置特殊节点。拷贝完成后,更新table为新数组 完成扩容。

putVal()添加元素:如果key val是空,抛出空指针异常;获取hash值;自旋:如果数组table没有初始化,则初始化数组;使用key的hash值和数组长度运算计算出数组的下标,如果这个位置的元素是空,直接cas把新元素添加到这个位置;如果元素的hash值是-1,则说明是占位节点,说明正在扩容,则帮助扩容(拷贝元素);否则synchronized锁定元素,对元素操作,如果是链表,则在链表中查找hash值相等,key相同的元素找到更新元素,找不到在尾部添加元素,如果是红黑树同样查找更新和添加。如果是链表,如果长度达到8,会做树化检查,如果数组长度不足64扩容否则树化。最后 增加元素数检查扩容。

get(Object key)获取值:首先计算key的hash值,使用hash值和数组长度运算出下标位置,取出该位置元素,如果元素是null,则返回null。否则 判断元素的hash值是否与key的hash值相等 key相同,如果是返回元素的值。否则 判断元素的hash值是否小于0 说明是树 在树中查找hash值相等key相同的元素,找到返回元素值,否则是链表 在链表中查找key的hash值相等 key相同的元素找到返回元素值。找不到返回null。

size()获取元素数量:增加元素数时候会cas更新basecount的值,如果高并发环境cas失败会把值保持在数组中,获取size时候会把basecount和数组中记录的数量相加得到元素数。

总结:HashMap是一种应用频率很高性能非常高的Map,但它不是现场安全的,在高并发环境会出现多线程put导致链表闭环,get无限循环问题。Hashtable和Collections.synchronizedMap是一种线程安全的Map,但是他们性能非常低,因为他们几乎对每个方法加synchronized,导致只能以独占的方式读写,吞吐量极低。ConcurrentHashMap是一种高性能线程安全的Map,它使用volatile变量 自旋cas和synchronied确保线程安全。1扩容:支持并发扩容,由一个线程创建新数组,多个线程一起拷贝元素到新数组,实现高效扩容。详细过程:先到达的线程,会创建一个新的数组,它的容量是原数组2倍,创建的新数组保存到volatile变量中,这样其他线程参与到扩容后会看到,不会去再次创建数组。然后对原数组进行分组处理,每组最少处理16个元素,一个线程处理一组,直到把原数组分组完,对要拷贝的元素先加synchronized锁,防止修改,进行拷贝到新数组的原位置或者原位置加原数组长度位置,如果原位置的元素是null则放置站位元素其hash值是-1,其他线程看到这种元素会直到现在正在扩容,会帮助扩容,拷贝完的位置也会放置这种占位元素,最终多个线程完成并发的拷贝完成扩容。2 put操作:先对key val进行非空检查,然后计算出key的hash值,自旋 如果数组还没初始化则进行初始化;使用hash值和数组长度运算出它所在的数组下标位置,取出该位置元素,如果是空,则直接使用Unsafe的原子性操作把新元素添加到这个位置;如果元素的hash值是-1,则该元素是占位符,说明此时正进行扩容,帮助扩容(拷贝元素);否则使用synchronized锁定元素,如果是链表,则在链表中查找hash值相等key相同的节点,找到修改val,找不到添加到链表尾部,如果是树做同样操作。如果是链表,会做树化检查,如果链表长度是8若数组长度不足64则扩容否则树化。最后对元素数加1,如果达到扩容阈值进行扩容。3 get非常简单,使用key的hash值和数组运算计算下标,得到下标位置上的元素如果是null则返回null否则去树或者链表中找key hash相等key相同的元素找到返回其值,找不到返回null。4 size 获取volatile变量元素数加上cas操作失败保存在数组中的元素数。它并不精确,因为在高并发环境中执行这添加移除元素,此时还没来的及更新数量。

猜你喜欢

转载自blog.csdn.net/liangwenmail/article/details/82775530
今日推荐