剑指Offer(类库)——HashMap、HashTable、ConcurrentHashMap(下)

HashTable

HashTable是线程安全的,那么HashTable做了什么操作才实现了HashMap没做到的线程安全呢???

写个简单的demo:
在这里插入图片描述
进入synchronizedMap内部看一下:
在这里插入图片描述
声明了一个mutex修饰对象的成员,使用互斥锁包围起来保证了内容互斥,串行化访问以此来保证线程安全。

HashTable也是一样的和HashMap实现逻辑没有什么区别,是用同样的去加锁而已:
在这里插入图片描述
但是串行化访问效率就会降低,那么既要保证线程安全又要保证效率要怎么办呢?这个时候ConcurrentHashMap就出现了。

ConcurrentHashMap

如果不用ConcurrentHashMap,那么要如何去优化HashTable呢???

通过锁粒度细化,整锁拆解成多个锁进行优化

在JDK5中的ConcurrentHashMap确实这么实现的,通过分段锁的Segment实现的结构是数组加上链表。而Segment可以对应头结点去存放默认的16把锁,区别是比起HashMap给每个头结点配一把锁,从串行化的HashTable简单对比效率提升了16倍。
在这里插入图片描述
如果7号锁下的线程去访问数组的话,其他节点占有的线程也可以去访问这些资源不会被阻塞,只有访问相同的Segment才会被阻塞。

但是每个bucket都用不同的锁去管理,性能上来说还是存在一定的缺陷,JDK8中ConcurrentHashMap使用CAS+synchronized使锁变得更加细粒度化,同时又进行了优化。

下图是ConcurrentHashMap的图解:

JDK8中ConcurrentHashMap随着最新的HashMap,使用了数组+链表+红黑树这种结构在效率上进行一定的提升。
在这里插入图片描述
下面来看一下ConcurrentHashMap的源码:

从成员变量来看,和HashMap存在相似之处:
在这里插入图片描述
最重要的是sizeCtl的成员变量,是JUC下面ConcurrentHashMap十分关键的一个成员变量。

是一个大小控制的标识符,哈希表初始化或者扩容的和时候的一个控制位标示量,负数代表正在进行初始化或者扩容操作,-1代表正在进行初始化,其他的负数代表有n-1个线程正在等待扩容,整合和为0代表还没有被初始化。

扫描二维码关注公众号,回复: 8971805 查看本文章

被volatile修饰,是多线程可见的:
在这里插入图片描述
ConcurrentHashMap是使用CAS+synchronized做到高效保证线程安全的,来看看put方法:

简单描述一下:

  1. 使用for循环,存在CAS操作要保证不断的去请求操作,之后判断是否为空,是空的话就去初始化;
  2. 如果不是空,进行哈希寻址找到头结点的存储位置;
  3. 如果这个位置不存在,使用CAS操作创建出来,添加失败break进入下一个循环;
  4. 如果发现这个key已经存在,说明ConcurrentHashMap正在进行remove,remove说明正在进行扩容就协助进行扩容,执行helpTransfer操作。
    在这里插入图片描述
    最后一种情况是哈希碰撞:

这个时候会锁住链表或者红黑二叉树的头结点,就是数组元素,操作如下:
在这里插入图片描述
此外ConcurrentHashMap还可以构建本地缓存来降低程序的计算量和负责度。。。
在这里插入图片描述
总结一下ConcurrentHashMap的put方法的实现逻辑:

  1. 判断Node[]数组是否进行了初始化,没有进行初始化操作;
  2. 通过hash定位数组的索引坐标是否有Node节点,如果没有使用CAS进行添加,添加失败进行下一次循环;
  3. 检查到内部正在扩容,就帮助一起扩容;
  4. 如果链表头结点是空,用synchronized锁住头结点;
    (1)、如果是Node进行链表的添加操作;
    (2)、如果是TreeNode进行树添加操作;
    (3)、如果是ReservationNode则抛出异常,ReservationNode和ConcurrentHashMap本地缓存相关;
  5. 判断链表长度达到8将链表转换成红黑树结构。

总结一下ConcurrentHashMap在JDK8和JDK5版本的差异:

总体还是使用的锁分离的思想就是比Segment更加细致,首先使用无锁操作CAS插入头结点,失败就循环重试,如果头结点已经存在就尝试获取头结点的同步锁再进行操作。

总结一下HashMap、HashTable、ConcurrentHashMap三者的区别:

  1. HashMap线程不安全,数据结构是数组+链表+红黑树;
  2. HashTable线程安全,数据结构是数组+链表;
  3. ConcurrentHashMap线程安全,使用CAS+同步锁,数据结构是数组+链表+红黑树;
  4. HashMap的key和value可以为null,其他两个不可以。
发布了242 篇原创文章 · 获赞 23 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44240370/article/details/104138608
今日推荐