Java并发(五)Java原子类和并发工具类

2024 年 5 月 29 日 星期三(已编辑)
/
72
2
这篇文章上次修改于 2024 年 7 月 5 日 星期五,可能部分内容已经不适用,如有疑问可询问作者。

Java并发(五)Java原子类和并发工具类

CAS

什么是CAS

CAS的全称为Compare-And-Swap,直译就是对比交换。

是一条CPU的原子指令,通过一个原子的操作,同时完成"读取内存,比较是否相等,修改内存”这三个步骤

CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口.

简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。

JDK中有哪些地方用到了CAS?请列举几个

  1. 自旋锁的实现
  2. Java原子类
  3. ReentrantLock的state改变
  4. 并发容器中:例如concurrentHashMap

CAS有什么问题和缺陷?

CAS方式为乐观锁,synchronized为悲观锁,因此使用CAS解决并发问题通常性能更优

乐观锁:乐观锁认为资源冲突的情况比较少见,在更新数据之前不会对资源加锁,而是在更新数据时先检查该资源是否已经被其他事务修改过。如果没有被修改,则顺利更新,如果检测到资源已经被修改,则认为发生了资源冲突,需要回滚事务并可能重试

悲观锁:悲观锁认为资源冲突的情况比较普遍,在访问资源之前先对资源加锁,阻塞其他事务对该资源的访问,直到当前事务释放该资源。

  1. ABA问题:一个值原来是A,变成B,又变成A,使用CAS检查是发现值没变,但是实际上变了
    • 有些业务不能容忍:例子:小明有100,取50,机器不好使点了两次,触发线程A和线程B,线程A(100->50)小明妈妈这时候转账50给小明(50->100),线程B又运行(100->50),小明被扣两次钱
    • 解决:给要修改的数据引入版本号,在CAS比较数据当前值和旧值的同时,也要比较版本号是否符合预期
      • 如果发现当前版本号和之前读到的版本号一致,就真正执行修改操作,并让版本号自增
      • 如果发现当前版本号比之前读到的版本号大,就认为操作失败
      • JDK的Atomict包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值
  2. 只能保证单个变量的原子性,不能保证代码块的原子性。要保证多个变量共同进行原子性操作,那只能借助别的方式,例如synchronized
  3. 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
    • 是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
    • 场景:
      • 限制对某个资源的并发访问数量,比如数据库连接池、线程池
  • Exchanger(交换者):是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换

    • 它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据
    • 场景:
      • 在两个线程之间传递数据,例如生产者-消费者模式中的数据交换

CyclicBarrier和CountDownLatch有什么区别?

  • CyclicBarrier是可重用的,其中的线程会等待所有的线程完成任务。届时,屏障将被拆除,并可以选择性地做一些特定的动作。CountDownLatch是一次性的,不同的线程在同一个计数器上工作,直到计数器为0
  • CyclicBarrier面向的是线程数;CountDownLatch面向的是任务数
  • 在使用CyclicBarriert时,你必须在构造中指定参与协作的线程数,这些线程必须调用await()方法;使用CountDownLatch时,则必须要指定任务数,至于这些任务由哪些线程完成无关紧要
  • CyclicBarrier可以在所有的线程释放后重新使用;CountDownLatch在计数器为0时不能再使用
  • 在CyclicBarrier中,如果某个线程遇到了中断、超时等问题时,则处于await的线程都会出现问题;在CountDownLatch中,如果某个线程出现问题,其他线程不受影响

CountDownLatch 适用于等待一组异步操作全部完成的场景,比如多线程计算一个结果,等所有子任务都完成后,主线程再汇总结果。

CyclicBarrier 适用于需要多个线程同时到达某个状态才能继续执行的场景,比如多线程模拟赛跑,每到达一个checkpoint就等待其他线程

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...