Java并发(五)Java原子类和并发工具类
CAS
什么是CAS
CAS的全称为Compare-And-Swap,直译就是对比交换。
是一条CPU的原子指令,通过一个原子的操作,同时完成"读取内存,比较是否相等,修改内存”这三个步骤
CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口.
简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。
JDK中有哪些地方用到了CAS?请列举几个
- 自旋锁的实现
- Java原子类
- ReentrantLock的state改变
- 并发容器中:例如concurrentHashMap
CAS有什么问题和缺陷?
CAS方式为乐观锁,synchronized为悲观锁,因此使用CAS解决并发问题通常性能更优
乐观锁:乐观锁认为资源冲突的情况比较少见,在更新数据之前不会对资源加锁,而是在更新数据时先检查该资源是否已经被其他事务修改过。如果没有被修改,则顺利更新,如果检测到资源已经被修改,则认为发生了资源冲突,需要回滚事务并可能重试
悲观锁:悲观锁认为资源冲突的情况比较普遍,在访问资源之前先对资源加锁,阻塞其他事务对该资源的访问,直到当前事务释放该资源。
- ABA问题:一个值原来是A,变成B,又变成A,使用CAS检查是发现值没变,但是实际上变了
- 有些业务不能容忍:例子:小明有100,取50,机器不好使点了两次,触发线程A和线程B,线程A(100->50)小明妈妈这时候转账50给小明(50->100),线程B又运行(100->50),小明被扣两次钱
- 解决:给要修改的数据引入版本号,在CAS比较数据当前值和旧值的同时,也要比较版本号是否符合预期
- 如果发现当前版本号和之前读到的版本号一致,就真正执行修改操作,并让版本号自增
- 如果发现当前版本号比之前读到的版本号大,就认为操作失败
- JDK的Atomict包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值
- 只能保证单个变量的原子性,不能保证代码块的原子性。要保证多个变量共同进行原子性操作,那只能借助别的方式,例如synchronized
- CAS会造成CPU利用率增加,因为CAS是不阻塞线程的,通常的用法是把CAS放到循环中(这样可以在资源竞争时自动重试,直到成功更新),所以如果竞争比较激烈的话,CPU消耗会显著增加
并发工具类
CountDownLatch,CyclicBarrier,Semaphore,Exchanger了解吗?
- CountDownLatch:倒计数器。
- 允许一个或多个线程等待其他线程(一或多组任务)完成操作
- 原理:构造AQS 的
state
值为count
。当线程使用countDown()
方法时,其实使用了tryReleaseShared
方法以 CAS 的操作来减少state
,直至state
为 0 - 场景:
- 等待多个异步操作完成后,再执行后续操作
- 实现"启动服务"时需要等待多个依赖服务启动完成的场景
- CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。
- 它允许一组线程相互等待,直到所有线程都到达某个公共屏障点,然后再继续执行
- 场景:
- 实现并行计算,当所有子任务都完成后,才继续执行后续操作。
- 定期执行检查点操作,当所有线程都到达检查点后,才继续执行
Semaphore(信号量):
- 原理: 构造AQS 的
state
值为permits
- 是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
- 场景:
- 限制对某个资源的并发访问数量,比如数据库连接池、线程池
- 原理: 构造AQS 的
Exchanger(交换者):是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换
- 它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据
- 场景:
- 在两个线程之间传递数据,例如生产者-消费者模式中的数据交换
CyclicBarrier和CountDownLatch有什么区别?
- CyclicBarrier是可重用的,其中的线程会等待所有的线程完成任务。届时,屏障将被拆除,并可以选择性地做一些特定的动作。CountDownLatch是一次性的,不同的线程在同一个计数器上工作,直到计数器为0
- CyclicBarrier面向的是线程数;CountDownLatch面向的是任务数
- 在使用CyclicBarriert时,你必须在构造中指定参与协作的线程数,这些线程必须调用await()方法;使用CountDownLatch时,则必须要指定任务数,至于这些任务由哪些线程完成无关紧要
- CyclicBarrier可以在所有的线程释放后重新使用;CountDownLatch在计数器为0时不能再使用
- 在CyclicBarrier中,如果某个线程遇到了中断、超时等问题时,则处于await的线程都会出现问题;在CountDownLatch中,如果某个线程出现问题,其他线程不受影响
CountDownLatch
适用于等待一组异步操作全部完成的场景,比如多线程计算一个结果,等所有子任务都完成后,主线程再汇总结果。
CyclicBarrier
适用于需要多个线程同时到达某个状态才能继续执行的场景,比如多线程模拟赛跑,每到达一个checkpoint就等待其他线程