CAS乐观锁

  • 乐观锁
    • 总是假设最好的情况,认为并发安全问题一定不会发生,所以不需要加锁。
    • 每次去拿数据的时候都认为别人不会修改,所以不会上锁。
    • 但是在最终更新数据的时候,会判断一下在此期间别人有没有更新过这个数据,如CAS算法的实现。
      • CAS算法主要分成3步:
      • 在这里插入图片描述
        • 获取共享数据的值,拷贝副本到工作内存,保存到值 current
        • 对工作内存中的共享数据进行操作,获得更新后的值 next
        • 执行CAS (compare and set)操作,先去主存获取当前共享变量的值value,与自己的旧值current比较
          • 如果二者相同,表示这段时间没有其他线程修改共享变量值,则同步 值 next 到主存
          • 如果二者不同,表示这段时间有其他线程修改了共享变量值,则放弃本轮修改,重新执行整个CAS。
          • 因此CAS一般写成一个while循环,修改成功则退出循环,否则就重试
          • 如 AtomicBoolean、AtomicInteger、AtomicLong 就是这个干的

CAS 比较与交换算法
在CAS(比较与交换)算法中涉及3个操作数:变量当前内存值V、变量的预期值E、新值U。只有该变量当前的内存值V与预期值E相同时,才会将新值U写入内存完成变量修改,否则什么都不做。下面是通过CAS修改变量数据的示例,CAS通过该变量的地址即可获取该变量当前的内存值V。当本轮CAS操作失败后,会重新读取该变量内存中最新的值并重新计算新值,直到其CAS操作修改变量成功为止。
do{
1. 读取变量值,记为E。用于写入修改时,判断该变量是否被修改
2. 使用读到的变量值E,计算该变量的新值,记为U
} while( !CAS(变量内存地址,E,U) )
在Java中,java.util.concurrent.atomic包的原子变量类大量使用了Unsafe类提供的CAS操作。进一步地,CAS操作通过硬件来保证了比较-更新操作的原子性。下面分别使用volatile和AtomicInteger来进行演示

public class CASDemo {
private static AtomicInteger atomicCount = new AtomicInteger(0);
private static volatile Integer count = 0;

public static void main(String[] args) throws InterruptedException {
    Thread[] threads = new Thread[20];
    for(int i=0; i<20; i++) {
        Thread thread = new Thread( ()->{
            for(int j=0; j<10000; j++) {
                atomicCount.incrementAndGet();  // atomicCount++
                count++;
            }
        } );
        threads[i] = thread;
        thread.start();
    }

    for(Thread thread : threads) {
        thread.join();
    }

    System.out.println("atomicCount: " + atomicCount);
    System.out.println("count: " + count);
}

}
从测试结果中,我们可以使用了CAS的原子类具备原子性
在这里插入图片描述

实现原理
前面我们说到CAS操作的原子性是通过硬件来保证,这里作进一步的解释说明。CPU层面上,CAS的比较-写入操作上是通过cmpxchg指令去实现完成的。然而不幸的是,cmpxchg指令并不是一个原子操作。即可能会发生这样的场景,线程A在执行cmpxchg指令的过程中,发现当前内存值V与预期值E一致,正准备将新值U写入内存,这个时候另外一个线程B打断了线程A的操作,将该变量修改了。显然这个变量发生了线程安全的问题,为此为了保证cmpxchg指令的原子性,不会被打断,需要在cmpxchg指令前添加一个前缀指令lock。通过对cmpxchg指令进行加锁(总线锁或缓存锁)来保证操作的原子性。通常我们会说CAS算法是一个无锁算法,但其实我们可以看到底层依然是加了锁的,只不过这个锁的粒度是很小的。
- CAS存在ABA问题:
- 当前线程只能感知共享变量的值有没有改变,假设别人从A改成了B,又改成了A,则当前线程感知不到,即ABA问题
- 可以使用版本号机制解决ABA问题,如原子类AtomicStampedReference,加入了版本号,每当修改值,版本号就会+1
- 如 AtomicStampedReference 加入了版本号,每当修改值,版本号就会+1。这样根据版本号就知道有没有别的线程修改过,而且还能知道修改过几次
- 再如 AtomicMarkableReference,他的版本号是一个boolean类型,所以只能判断是否修改过值,不知道修改过几次。
AtomicMarkableReference只能缓解ABA问题,如果别人修改标记后,又改回来,则无法感知。

  • 乐观锁适用于多读的应用类型,这样可以提高吞吐量
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值