ConcurrentHashMap

参考:  sizeCtl : https://blog.csdn.net/Unknownfuture/article/details/105350537

  1.            https://www.jianshu.com/p/c0642afe03e0
  2.            https://www.cnblogs.com/renqiqiang/p/9451301.html
  3.            https://www.jianshu.com/p/c0642afe03e0
  4.          深入理解Java——ConcurrentHashMap源码的分析(JDK1.8)  https://cloud.tencent.com/developer/article/1417971
  5.          UnSafe :  https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html

并发 

  1. UnSafe类 保证并发线程安全操作
  2. synchronized 

数据结构:

table 数组

加了volatile, 保证线程可见性;

初始化table数组  ,  如何保证并发场景下仍能初始化一个数组?

  • 通过CAS保证

先看一个重要字段 sizeCtl , 数组初始化的真实长度,即table数组申请内存的长度;

  • 默认0 , 
  • 构建Map时,构造方法里会初始化该值

  • CAS修改该值

initTable()方法

/**
     * The default initial table capacity.  Must be a power of 2
     * (i.e., at least 1) and at most MAXIMUM_CAPACITY.
     */
    private static final int DEFAULT_CAPACITY = 16;

    /**
     * Initializes table, using the size recorded in sizeCtl.
     */
    private final ConcurrentHashMap.Node<K,V>[] initTable() {
        ConcurrentHashMap.Node<K,V>[] tab;
        int sc;
        //自旋,保证一定会初始化table成功;
        while ((tab = table) == null || tab.length == 0) {
            /**
             * sizeCtl取值
             * sizeCtl > 0,  会在ConcurrectHashMap的构造方法中赋值, 即由指定的初始容量 通过tableSizeFor()方法 计算得来,
             * sizeCtl = 0, 未指定初始容量
             * sizeCtl = -1, 表示table数组正在进行初始化; 即下面  U.compareAndSwapInt(this, SIZECTL, sc, -1) 通过CAS将sizeCtl变量置位-1;
             * sizeCtl = -N, 取-N对应的二进制的低16位数值为M,此时有M-1个线程进行扩容。
             */
            if ((sc = sizeCtl) < 0)
                //使当前线程由执行状态,变成为就绪状态,让出cpu时间片,在下一个线程执行时候,此线程有可能被执行,也有可能没有被执行。
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                //CAS , 如果sizeCtl >= 0, 说明未初始化和准备初始化,通过CAS置位-1, 保证并发;
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        // 如果sc == 0, 则使用默认初始容量 16
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        ConcurrentHashMap.Node<K,V>[] nt = (ConcurrentHashMap.Node<K,V>[])new ConcurrentHashMap.Node<?,?>[n]; //初始化Node数组
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //还原sizeCtl值, 因为上面通过CAS将sizeCtl改为-1了,这里进行还原
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

下面主要看下,当线程 t1 进入initTable(); 的时候,这时线程 t2 也符合tab == null添加下则进入 initTable();方法,这个时候如何保证 t1,t2 扩容时候的线程安全;

保证方式:其实首次对map进行扩容的时候,即初始化table变量的时候,只需要保证第一个线程进入时进行初始化,其他线程无法执行即可。

这时通过CAS保证update只有一个线程成功即可。

下面看看 initTable() 这个方法的实现方案:

if ((sc = sizeCtl) < 0)   // 初始化时为0   当为负数的时候线程 进入yield()方法
    Thread.yield();     // yield()方法会通知线程调度器放弃对处理器的占用,当前线程放弃执行权

如果发生Hash冲突,如何保证线程安全的写入?

通过 synchronized 关键字,加重量级锁;

Node节点:

被Volatile修饰,保证线程间可见。

Q: Volatile如何保证可见性?

Volatile的实现原理,使用到了内存屏障(Memory Barrier);      参考: https://www.jianshu.com/p/d3fda02d4cae

内存屏障的作用或者效果: 

  1. 保证指令不会被重排序;
  2. 影响某些数据的内存可见性;

如果一个变量是volatile修饰的,则 JMM(Java Memory Model)Java内存模型会保证

这个字段变量前, 插入一条Read-Barrier 指令, 该指令会强制刷新缓存cache,即 各个线程本地缓存强制刷新到Jvm 主内存, 

这个变量后, 插入一条 Write-Barrier 指令, 该指令会强制把本地线程的缓存数据刷新到JMM主内存;

不同线程在操作一个变量对象时,会从主内存里拷贝一份到自己的本地缓存里, 也叫线程本地变量,对线程本地变量的数据进行处理, 并不会立即刷新到主内存;volatile关键字 使用的内存屏障,通过Read-BarrierWrite-Barrier 指令, 保证了主内存数据的一致性;实现了可见性;

每个线程都有自己的本地内存Local Memory(只是一个抽象概念,物理上不存在),存储了该线程的共享变量副本,

所以,线程A和线程B之前需要通信的话,必须经过一下两个步骤:
1、线程A把本地内存中更新过的共享变量刷新到主内存中。
2、线程B到主内存中读取线程A之前更新过的共享变量。

构造方法

可见, ConcurrentHashMap与HashMap一样, 都是懒加载, 使用的时候才会创建hash桶;

put()方法

https://www.jianshu.com/p/c0642afe03e0

put()方法流程:

  1. 检查key、value是否空, 为空则抛异常;
  2. 通过key, 计算hash值;
  3. 自旋
  4. 增加总数

在第3步自旋时,又做了如下:

  1. 判断表是否空, 如果则初始化,自旋重试;
  2. CAS判断hash相应位置是否无元素, 如果无,则新插入, 否则,自旋重试;
  3. 相应hash位有元素, 判断其hash值是否是-1, 如果是, 则说明其他线程正在扩容, 则一起扩容,然后自旋重试;
  4. 对一个元素加锁 synchronized(f); put数值

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值