Java并发 --- 原子变量

Java 并发 --原子变量

原子变量出现的原因 --并发

锁的劣势

  1. Sysnred
    当多个线程争用锁时,JVM 会借助操作系统的功能挂起线程,等待之后再被恢复(如果用了自旋锁就不一定会被挂起),这里的主要开销就来自于线程的挂起和恢复。除此之外,锁还有可能会带来类似死锁、活跃性等问题。

  2. Voliet 劣势
    与锁相比,volatile 变量的使用不会发生上下文切换和线程调度,因此它是轻量级的。它提供了「可见性」,但不能用于构造原子的复合操作,继而也就不能实现计数器或互斥体等工具(i++ 这种自增操作在没有原子变量之前,只能通过加锁来保证数据一致性)

我们希望既可以有类似 volatile 的机制,操作是轻量级的,又可以支持原子操作,这就要借助现代处理器中的一些机制。

  • 原子是一个最小的变量
  • 原子在java Jdk 中存在的位置
    • 在java.util.concurrent.atomic 中,里面包含了常见的原子类型

CAS(Compare-and-Swap)

CAS 操作在修改目标变量 V 的时候,它先测试 V 的当前值和我们的预期值是否相同,如果相同就设置 V 为新的值。

它的最经典的使用模式就是:

  • 从变量 V 中读取值 A

  • 根据值 A 计算得到新的值 B

  • 通过 CAS 操作,比较变量 V 当前的值是否还是 A:如果是,就将 V 设置为 B;如果不是,操作失败,一般不做任何额外操作,只是再次从第一步开始重新尝试。

在竞争不高时,基于 CAS 的计时器在性能上远远超过基于锁的计数器。它的主要缺点在于,使用 CAS 操作我们需要自己处理竞争问题(例如以上代码中 compareAndSet 方法返回 false 的情况下,我们要再次循环尝试),而使用锁的情况下,这种竞争问题是底层自动处理的。

原子变量类

  • 原子变量实际已经封装了其所有需要的操作

锁和原子变量的性能比较

比较结论是:在高度竞争的情况下,锁的性能超过原子变量的性能;但在更真实的竞争情况下则反过来。原因是,高度竞争情况下,CAS 操作失败之后会立即重试,这会导致更多的竞争,而锁会挂起线程,降低 CPU 的使用率和共享内存总线上的通信量。

这一点在 Redis 中也是同样的,Redis 可以通过 WATCH 来实现 CAS 操作,继而实现事务,但是在系统负载很重,主键争用非常厉害的时候,用 WATCH/MULTI/EXEC 组成的乐观锁性能会严重下降,这种情况用悲观锁效率更高。

ABA 问题

ABA 问题存在于 CAS 操作中,如果我们的主线程第一次获取了变量 V 的值 A,然后变量 V 先被其他线程改为值 B,又被改为值 A,那么当我们主线程执行 CAS 操作的时候,发现变量 V 的值还是 A,并没有发生变化,那这个判断正确么?

至少在某些情况下这是错误的,因此,我们可以额外维护一个原子变量 version(版本号),每次修改都会让版本号递增,我们可以通过配合版本号来判断值是否发生变化。在 ElasticSearch 中,就是借助 version 来实现 CAS 操作,也就是实现一个乐观锁。

猜你喜欢

转载自blog.csdn.net/yuansiyuan123/article/details/83959066