落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单( 二 )

  • T1线程还没有完成数组的初始化工作 , T2线程已经开始执行putVal()方法了 , 这个时候T2线程put进去的值还是T1线程初始化的那个数组吗?
  • 也就是在多线程的环境下操作可能会和单线程操作的属于不一致 , 即所谓的线程安全问题 。
    如何解决呢?是不是可以简单粗暴的对这个put()方法加锁?当一个线程执行put()方法的时候 , 让其他线程只能等待 。
    HashTable就是这样做的 , 可以打开HashTable的源码看到其put()方法如下:
    落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单HashTable的put()方法
    Collections.synchronizedMap()中是定义了一个内部类SynchronizedMap:
    包含传入的Map以及定义的Object类型的mutex对象 。
    所有的方法都必须获得mutex对象的锁才能执行下去:
    落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单synchronized (mutex)
    换个思路理解就是:在Map对象的外面装了一把锁 , 想访问Map对象必须获得mutex这把锁才可以 。
    所以我们可以知道的是:
    无论是HashTable还是Collections.synchronizedMap()都是通过synchronized关键字来保证线程安全的 。
    即使现在的synchronized关键字在锁的优化上有了很大的性能提升 , 但是依旧不可避免的情况是:
    在多线程竞争激烈的情况下 , 锁会升级至重量级锁 , 重量级锁是由操作系统所持有和分配的 , 也就意味着出现了用户态(我们的JVM进程)和内核态(操作系统的kernel进程)的切换 , 性能也就随之降低!
    那么有没有那么一种方法避免这样的情况出现呢?当然有!不然咱们还聊什么?
    这就是CAS , Compare And Swap!
    也就是本文的主角ConcurrentHashMap的实现方式 , 它是采用了CAS无锁化的方式来保证了数组初始化的线程安全
    落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单U.compareAndSwapInt(this, SIZECTL, sc, -1)
    最核心的就是上面的initTable()方法了 , 最核心的就是这句话:
    最核心的就是上面的**initTable()**方法了 , 最核心的就是这句话:同时要注意的是这里的几个成员变量都是volatile修饰的:
    transient volatile Node[] table;private transient volatile int sizeCtl;4、其他线程不安全的操作上面主要是通过数组初始化这一步来讲HashMap是如何线程不安全的 , 并且ConcurrentHashMap中是如何保证线程安全的 。
    HashMap中线程不安全的地方 , ConcurrentHashMap中找对应功能的地方 , 看是怎么保证线程安全的就可以了 。
    接下来我们继续看HashMap源码中那些线程不安全的操作 , 继续看putVal()方法:
    落叶知秋|ConcurrentHashMap确实很复杂,这样学源码才简单看图片里的代码注释
    大家如果细看上面的源码以及我写的注释的话 , 会发现初始化数组后 , 就开始要把对象put到刚刚初始化的数组中去了:
    if ((p = tab[i = (n - 1) 那么上面这句话是线程安全的吗?
    当然不是 , 可以这么说 , 这种带if判断的都不是线程安全的 。
    当一个线程判断当前这个tab[i]节点是null的时候 , 但是还没有执行完下一句 , 这个时候另外一个线程也来了 , 他也判断这个节点不是null , 也执行下面那句话 , 那最终两个线程只能有一个数据能写进去 , 另一个就丢了 。


    推荐阅读