java1.8hashmap源码_ConcurrentHashMap基于JDK1.8源码剖析

前言

本篇文章将会带大家分析 ConCurrentHashMap 的源码

一、ConCurrentHashMap剖析

ConCurrentHashMap 是 Java 中比较重要的一个类,也是面试经常会问到的一个类。

1.1初识ConCurrentHashMap

ConCurrentHashMap的底层是:散列表+红黑树,与HashMap是一样的。

0054aab1-0087-44b5-aa3a-e3ea30f99424.png

我简单翻译了一下的注释:

3cbc165a-69a9-403f-a99a-1df2440d9200.png

根据上面注释我们可以简单总结:

JDK1.8底层是散列表+红黑树

ConCurrentHashMap 支持高并发的访问和更新,它是线程安全的

检索操作不用加锁,get 方法是非阻塞的

key和value都不允许为null

1.2JDK1.7底层实现

上面指明的是JDK1.8底层是:散列表+红黑树,也就意味着,JDK1.7的底层跟JDK1.8是不同的~

JDK1.7的底层是:segments+HashEntry数组:

bb0b033c-5985-49bc-ad36-460864b04928.png

Segment继承了ReentrantLock,每个片段都有了一个锁,叫做“锁分段”

大概了解一下即可~

1.3有了Hashtable为啥需要ConCurrentHashMap

Hashtable是在每个方法上都加上了 Synchronized 完成同步,效率低下。

ConcurrentHashMap 通过在部分加锁和利用CAS算法来实现同步。

1.4CAS算法和volatile简单介绍

在看 ConCurrentHashMap 源码之前,我们来简单讲讲 CAS 算法和 volatile 关键字

CAS(比较与交换,Compare and swap) 是一种有名的无锁算法

CAS有3个操作数

内存值V

旧的预期值A

要修改的新值B

当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做- 当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值(A和内存值V相同时,将内存值V修改为B),而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试(否则什么都不做)

看了上面的描述应该就很容易理解了,先比较是否相等,如果相等则替换(CAS算法)

接下来我们看看volatile关键字,在初学的时候也很少使用到volatile这个关键字。反正我没用到,而又经常在看Java相关面试题的时候看到它,觉得是一个挺神秘又很难的一个关键字。其实不然,还是挺容易理解的~

volatile经典总结:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性

我们将其拆开来解释一下:

保证该变量对所有线程的可见性 在多线程的环境下:当这个变量修改时,所有的线程都会知道该变量被修改了,也就是所谓的“可见性”

不保证原子性 修改变量(赋值)实质上是在JVM中分了好几步,而在这几步内(从装载变量到修改),它是不安全的。

1.5ConCurrentHashMap域

域对象有这么几个:

be1dadb2-a2e4-49dc-b347-9696b606411b.png

我们来简单看一下他们是什么:

d9c02a29-d8c9-4d3c-a50c-8f4c1a27690c.png

1.6ConCurrentHashMap构造方法

ConcurrentHashMap的构造方法有5个:

da2ae35b-d357-4b7f-8229-d27910303d7c.png

具体的实现是这样子的:

51150b28-78ea-4fce-a11b-5a2ae52f8858.png

可以发现,在构造方法中有几处都调用了 tableSizeFor() ,我们来看一下他是干什么的:

点进去之后发现,啊,原来我看过这个方法,在HashMap的时候.....

f7a53ea9-dac9-4ff2-9455-f058ea75c7bf.png

它就是用来获取大于参数且最接近2的整次幂的数...

赋值给sizeCtl属性也就说明了:这是下次扩容的大小~

1.7put方法

终于来到了最核心的方法之一:put方法啦~~~~

我们先来整体看一下put方法干了什么事:

5be7f053-9932-41aa-a2e9-54c9a6ddee01.png

接下来,我们来看看初始化散列表的时候干了什么事: initTable()

62af9466-897b-485d-a3a0-0c1f788a3e89.png

只让一个线程对散列表进行初始化!

1.8get方法

从顶部注释我们可以读到,get方法是不用加锁的,是非阻塞的。

我们可以发现,Node节点是重写的,设置了volatile关键字修饰,致使它每次获取的都是最新设置的值

a3e77cf9-8d59-4ba0-bb0a-0a705e4dadf2.png

51ca2b87-8aff-443b-a354-eb9117164ee4.png

二、总结

上面简单介绍了ConcurrentHashMap的核心知识,还有很多知识点都没有提及到,有兴趣进入的同学可到下面的链接继续学习。

下面简单总结一下ConcurrentHashMap的核心要点:

底层结构是散列表(数组+链表)+红黑树,这一点和HashMap是一样的。

Hashtable是将所有的方法进行同步,效率低下。而ConcurrentHashMap作为一个高并发的容器,它是通过部分锁定+CAS算法来进行实现线程安全的。CAS算法也可以认为是乐观锁的一种~

在高并发环境下,统计数据(计算size...等等)其实是无意义的,因为在下一时刻size值就变化了。

get方法是非阻塞,无锁的。重写Node类,通过volatile修饰next来实现每次获取都是最新设置的值

ConcurrentHashMap的key和Value都不能为null

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值