这是我参与8月更文挑战的第31天,活动详情查看:8月更文挑战
前言
关于HashMap,其实问的比较多的还是HashMap和ConcurrentHashMap的区别,这样一连串的问下去,一环接一环;先问问区别,然后按着线程安全问下去,这样一直以来,考验了基础又看当前面试者的能力
历练
上一篇文章,主要讲述的是HashMap以及 关于JDK1.7的ConcurrentHashMap结构与安全性,本篇文章将着重讲解JDK1.8的ConcurrentHashMap
什么是CAS ? 自旋又是什么?
在此之前,需要先了解一下,什么是CAS,以及CAS的实现---自旋
CAS 是乐观锁的一种实现方式,是一种轻量级锁,JUC中很多工具类的实现都是基于CAS;
CAS的操作流程是:
- 线程在读取数据时不进行加锁
- 在准备执行修改数据时,比较原值是否已经被修改,若未被修改则写入成功;若已经被修改则报错或者重新走读取数据
- 这是一种客观的策略,认为并发操作不总是发生
CAS就一定能保证数据没被其他线程修改吗
并不是的,比如最经典的ABA问题,CAS就没办法判断了;
一个线程修改了值为B,另一个线程又把值修改为A,对于这个时候判断的线程,就只能发现值还是A,所以不知道这个值到底有没有被修改过。其实如果只追求最后结果正确,这是OK的。
但是实际过程中还是需要记录修改的过程的,比如资金修改、日志监控这些业务,都是需要记录过程的,方便追溯。
如何解决ABA问题?
用版本号去保证,在数据库加一个字段,VersionNumber,每次判断的时候校验版本号,操作成功则+1(where num = #{num})
除此以外,时间戳也可以做到,查询的时候把时间戳也查出来,对比一致才修改,并且更新当前时间;
方法很多。跟版本号异曲同工,看业务场景的设计
既然用锁或 synchronized 关键字可以实现原子操作,那么为什么还要用 CAS 呢
因为加锁或使用 synchronized 关键字带来的性能损耗较大,而用 CAS 可以实现乐观锁,它实际上是直接利用了 CPU 层面的指令,所以性能很高。 synchronized 之前一直都是重量级的锁,但是后来Java官方是有对他进行升级的,现在采用的是锁升级的方式去做的
针对synchronized 获取锁的方式,JVM使用了锁升级的优化方式,就是使用偏向锁优先同一线程然后再次获取锁,如果失败,则升级为CAS轻量级锁,如果失败就会短暂自旋,防止线程被系统挂起。如果最后都失败了就升级为重量级锁。
很多重量级都是由轻量级的方式锁定的。
关于JDK1.8 的ConcurrentHashMap
-
Put()方法
-
Get()方法
-
计算hash值,定位到该table的索引位置,如果是首节点符合就返回
-
如果是红黑树就遍历;链表也遍历
-
总结
关于面试常见的集合三连,这里只是小打小闹,真正的了解+掌握,还得多实践+多去巩固