concurrenthashmap_JDK1.7的ConcurrentHashMap与JDK1.8的ConcurrentHashMap

除了Map系列应该有的线程安全的get,put等方法外,ConcurrentHashMap还提供了一个在并发下比较有用的方法 putIfAbsent.

如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回null。

V 

JDK1.7的ConcurrentHashMap实现原理

1、底层:

(1)底层数据结构:

<jdk1.7>:数组(Segment) + 数组(HashEntry) + 链表(HashEntry节点)

底层一个Segments数组,存储一个Segments对象,一个Segments中储存一个Entry数组,存储的每个Entry对象又是一个链表头结点。

3dcea38fec5df1a2a8df0d107ca54d3b.png

(2)基本属性:

两个主要的内部类:

class Segment内部类,继承ReentrantLock,有一个HashEntry数组,用来存储链表头结点

public 

(3)主要方法:

get():

1、第一次哈希 找到 对应的Segment段,

调用Segment中的get方法

2、再次哈希找到对应的链表,

3、最后在链表中查找。

public 

此方法没有加锁,但是HashEntry结构中的value前面又volatile关键字来保证可见性、有序性。但不保证原子性。

put():

1、首先确定段的位置,然后调用Segment中的put方法:

2、加锁

3、检查当前Segment数组中包含的HashEntry节点的个数,如果超过阈值就重新hash

4、然后再次hash确定放的链表。

5、在对应的链表中查找是否相同节点,如果有直接覆盖,如果没有将其放置链表尾部

public 

size():

1、首先对segments进行两次统计操作,如果两次的统计值相同,则说明这段时间没有线程来访问这个map,拿到的size大小是正确的,返回。

2、如果前两次拿到的值不相同,第三次就先对每一个segment进行加锁,然后在整体遍历计算一次,最后释放锁。所以在并发度比较高的情况之下,不建议使用这个方法,会把所有的segment都加锁。类似的还有containsValue(Object value)这个方法,如果我们只需要判断当前map不为空,可以用isEmpty()方法,它是不加锁的。

public 

ConcurrentHashMap():

public 

这里面两点要说明的,在上面代码的注释中也有提到:

初始化大小一定是2的指数级

初始化segments数组的时候只会对segments[0]进行初始化,其他结点的初始化是在put操作的时候。

4) 重哈希方式 :重点:

重哈希的方式 :只是对 Segments对象中的HashEntry数组进行重哈希,ConcurrentHashMap初始化之后就不可更改segments数组大小,默认的初始化值为16,那么它的并发度也是16,如果初始化时给segments定义的太大,也会造成命中的效率下降,影响整体的性能,一般默认的也够用了。

2、通过什么保证线程安全

分段锁 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。


JDK1.8的ConcurrentHashMap实现原理

1、底层:

(1)底层数据结构:

<jdk1.8>:数组(Segment + 链表/红黑树,类似于hashMap<jdk1.8>

Node数组使用来存放树或者链表的头结点,当一个链表中的数量到达一个数目时(8),会使查询速率降低,所以到达一定阈值时,会将一个链表转换为一个红黑二叉树,提高查询的速率。

8998924ca21ea94c4e355ef9fff192f9.png

(2)基本属性:

public 

(3)主要方法:

ConcurrentHashMap():

public 

初始化不做任何操作,也不会初始化table数组,在调用put()方法的时候判断table[]如果为null,进行初始化。

put():

public 

get():

public 

size():

public 

并发下的Map常见面试题汇总:

HashMap 和 HashTable 有什么区别?

HashMap 线程不安全,HashTable 线程安全(每个方法前面有一个synchronized),前者效率高一些

HashMap 是允许key或者value为null,HashTable 不允许,ConcurrenotHashMap也是不允许的

HashMap 默认初始化大小为16,haHashTable为11

扩容时,HashMap缺省时扩大两倍,而HashMap 扩大两倍+1

HashMap散列时会重新计算hash,HashTable 直接使用它的hashCode值

Java 中的另一个线程安全的与 HashMap 极其类似的类是什么?同样是线程安全,它与 HashTable 在线程同步上有什么不同?

与 HashMap 极其类似的类是ConcurrentHashMap

HashTable是锁整张表,在高并发的情况下效率低一些,ConcurrentHashMap 1.7采用分段锁,1.8中采用cas+synchronized +分段锁大大提升了并发效率。

HashTable是强一致,在get时直接锁整张表,而ConcurrentHashMap 在get时是不加锁的,可能在拿的过程中数据被改,是弱一致的。

HashMap & ConcurrentHashMap 的区别?

前者线程不安全,后者线程安全

前者允许key or value值为空,后者不允许key or value值为空

在1.8中他们的红黑树TreeNode继承不同,HashMap的TreeNode继承自LinkedHashMap.Entry,而ConcurrentHashMap 的TreeNode继承自Node

ConcurrentHashMap 锁机制具体分析(JDK 1.7 VS JDK 1.8)?

1.7中底层采用数组+数组+链表结构,使用Segment和HashEntry类,而Segment继承自可重入的ReentrantLock同步锁来充当锁的角色,由每个segment保护自己所属的数据,而HashEntry所组成的一个数组又在segment下面

1.8采用node+cas+synchronized

ConcurrentHashMap 在 JDK 1.8 中,为什么要使用内置锁 synchronized 来代替重入锁 ReentrantLock?

1.开发团队在jdk1.8上对synchronized 做了大量的性能优化,而且基于虚拟机本来的关键字优化空间更大,更加自然;

2.显示锁是一个对象,要消耗内存,而synchronized 作为一个语言特性,它的内存消耗会更少

1.8下ConcurrentHashMap 简单介绍?

数据结构;

1.重要成员变量;sizeCtrl,控制初始化大小以及扩容大小,它也表示当前是否正在扩容

2.重要数据结构:node:table[]中的基本数据结构

treeNode, treeBin:为了支持红黑树,扩展出来的继承Node

方法:

put:如果没有初始化,先进性初始化,直接插入用cas,如需要扩容,先进行扩容,如果hash冲突,对当前node结点进行加锁,链表以尾插法的形式插入,红黑树按照红黑树的方式插入,如果链表长度大于8,转红黑树,如果size>sizeCtrl,则进行扩容。

get:分别按照链表或者红黑树的结构去查找数据

并发扩容,每个线程以步长的形式对结点进行扩容。

ConcurrentHashMap 的并发度是什么?

1.7中同时更新ConcurrentHashMap,并且不产生锁竞争的最大线程并发数,默认为16,而且可以在构造函数中设置,但是 ConcurrentHashMap会使用>=当前设置值的最小2^n作为实际的并发度。

1.8中并发度则无实际的用处,若初始容量小于并发度,将初始容量提升至并发度大小。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值