CAS机制与自旋锁

CAS(Compare-and-Swap),即比较并替换,java并发包中许多Atomic的类的底层原理都是CAS。

它的功能是判断内存中某个地址的值是否为预期值,如果是就改变成新值,整个过程具有原子性。

具体体现于sun.misc.Unsafe类中的native方法,调用这些native方法,JVM会帮我们实现汇编指令,这些指令是CPU的原子指令,因此具有原子性。

 1 public class CASDemo {
 2
 3     public static void main(String[] args) {
 4
 5         //初始值5
 6         AtomicInteger atomicInteger = new AtomicInteger(5);
 7
 8         //和5比较,设置为10
 9         System.out.println("预期值:5,当前值:"+atomicInteger);
10         System.out.println("是否设置成功:"+atomicInteger.compareAndSet(5, 10));
11         //和5比较,设置为15
12         System.out.println("预期值:5,当前值:"+atomicInteger);
13         System.out.println("是否设置成功:"+atomicInteger.compareAndSet(5, 15));
14
15         System.out.println("当前值:"+atomicInteger);
16     }
17 }

输出为:

预期值:5,当前值:5
是否设置成功:true
预期值:5,当前值:10
是否设置成功:false
当前值:10

下面看一下getAndAddInt在底层Unsafe类中的代码(自旋锁),运用到了CAS

//va1为对象,var2为地址值,var4是要增加的值,var5为当前地址中最新的值public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

首先通过volatile的可见性,取出当前地址中的值,作为期望值。如果期望值与实际值不符,就一直循环获取期望值,直到set成功。

适用场景:

  1. CAS 适合简单对象的操作,比如布尔值、整型值等;

  2. CAS 适合冲突较少的情况,如果太多线程在同时自旋,那么长时间循环会导致 CPU 开销很大;

CAS的缺点:

  1. CPU开销过大 : 在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。

  2. 不能保证代码块的原子性:CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

  3.  ABA问题:如果内存地址V初次读取的值是A,在CAS等待期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。

ABA问题以及解决:使用带版本号的原子引用AtomicStampedRefence<V>,或者叫时间戳的原子引用,类似于乐观锁。

 0 // ABA问题及解决方式 1 public class ABADemo {
 2
 3     private static AtomicReference<String> atomicReference = new AtomicReference<>("A");
 4     private static AtomicStampedReference<String> stampReference = new AtomicStampedReference<>("A",1);
 5
 6     public static void main(String[] args){
 7         new Thread(()->{
 8             //获取到版本号
 9             int stamp = stampReference.getStamp();
10             System.out.println("t1获取到的版本号:"+stamp);
11             try {
12                 //暂停1秒,确保t1,t2版本号相同
13                 TimeUnit.SECONDS.sleep(1);
14             } catch (InterruptedException e) {
15                 e.printStackTrace();
16             }
17             atomicReference.compareAndSet("A","B");
18             atomicReference.compareAndSet("B","A");
19
20             stampReference.compareAndSet("A","B",stamp,stamp+1);
21             stampReference.compareAndSet("B","A",stamp+1,stamp+2);
22             System.out.println("t1线程ABA之后的版本号:"+stampReference.getStamp());
23
24         },"t1").start();
25
26         new Thread(()->{
27             //获取到版本号
28             int stamp = stampReference.getStamp();
29             System.out.println("t2获取到的版本号:"+stamp);
30             try {
31                 //暂停2秒,等待t1执行完成ABA
32                 TimeUnit.SECONDS.sleep(2);
33             } catch (InterruptedException e) {
34                 e.printStackTrace();
35             }
36             System.out.print("普通原子类无法解决ABA问题: ");
37             System.out.println(atomicReference.compareAndSet("A","C")+"\t"+atomicReference.get());
38             System.out.print("版本号的原子类解决ABA问题: ");
39             System.out.println(stampReference.compareAndSet("A","C",stamp,stamp+1)+"\t"+stampReference.getReference());
40
41         },"t2").start();
42     }
43 }

输出结果:普通原子引用类在另一个线程完成ABA之后继续修改(把A改成了C),带版本号原子引用有效的解决了这个问题。

t1获取到的版本号:1
t2获取到的版本号:1
t1线程ABA之后的版本号:3
普通原子类无法解决ABA问题: true    C
版本号的原子类解决ABA问题: false    A

原文地址:https://www.cnblogs.com/dream2true/p/10759763.html

时间: 2024-10-12 07:05:52

CAS机制与自旋锁的相关文章

CAS自旋锁

什么是自旋锁 自旋锁要从多线程下的锁机制说起,由于多处理器系统环境中有些资源因为其有限性,有时需要互斥访问(mutual exclusion),这时会引入锁的机制,只有获取了锁的进程才能获取资源访问.即每次只能有且只有一个进程能获取锁,才能进入自己的临界区,同一时间不能两个或两个以上进程进入临界区,当退出临界区时释放锁. 设计互斥算法时总是会面临一种情况,即没有获得锁的进程怎么办? 通常有2种处理方式: 一种是没有获得锁的调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,这就是本文的重点

多线程之美6一CAS与自旋锁

1.什么是CAS CAS 即 compare and swap 比较并交换, 涉及到三个参数,内存值V, 预期值A, 要更新为的值B, 拿着预期值A与内存值V比较,相等则符合预期,将内存值V更新为B, 不相等,则不能更新V. 为什么预期值A与内存值V不一样了呢? 在多线程环境下,对于临界区的共享资源,所有线程都可以访问修改,这时为了保证数据不会发生错误,通常会对访问临界区资源加锁,同一时刻最多只能让一个线程访问(独占模式下),这样会让线程到临界区时串行执行,加锁操作可能会导致并发性能降低,而循环

菜鸟nginx源码剖析数据结构篇(十) 自旋锁ngx_spinlock

Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 11th, 2014 自旋锁(Spinlock)是一种 Linux 内核中广泛运用的底层同步机制.自旋锁是一种工作于多处理器环境的特殊的锁,在单处理环境中自旋锁的操作被替换为空操作.当某个处理器上的内核执行线程申请自旋锁时,如果锁可用,则获得锁,然后执行临界区操作,最后释放锁:如果锁已被占用,线程并不会转入睡眠状态,而是忙等待

多线程编程之自旋锁

一.什么是自旋锁 一直以为自旋锁也是用于多线程互斥的一种锁,原来不是! 自旋锁是专为防止多处理器并发(实现保护共享资源)而引入的一种锁机制.自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用.无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁.但是两者在调度机制上略有不同.对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态.但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁

并发编程的灵魂:CAS机制详解

Java中提供了很多原子操作类来保证共享变量操作的原子性.这些原子操作的底层原理都是使用了CAS机制.在使用一门技术之前,了解这个技术的底层原理是非常重要的,所以本篇文章就先来讲讲什么是CAS机制,CAS机制存在的一些问题以及在Java中怎么使用CAS机制. 其实Java并发框架的基石一共有两块,一块是本文介绍的CAS,另一块就是AQS,后续也会写文章介绍. 什么是CAS机制 CAS机制是一种数据更新的方式.在具体讲什么是CAS机制之前,我们先来聊下在多线程环境下,对共享变量进行数据更新的两种模

(转)Java锁、自旋锁、CAS机制

转自:http://www.jb51.net/article/55381.htm 转自:http://blog.csdn.net/aesop_wubo/article/details/7537278 ReentrantLock实现原理源码分析(推荐): http://huangyunbin.iteye.com/blog/1942313 1. Unsafe类:java不能直接访问操作系统底层,而是通过本地方法来访问.Unsafe类提供了硬件级别的原子操作. 2. ReentrantLock实现原理

Java线程 - CAS自旋锁(spin-lock)

一.自旋锁提出的背景 由于在多处理器系统环境中有些资源因为其有限性,有时需要互斥访问(mutual exclusion),这时会引入锁的机制,只有获取了锁的进程才能获取资源访问.即是每次只能有且只有一个进程能获取锁,才能进入自己的临界区,同一时间不能两个或两个以上进程进入临界区,当退出临界区时释放锁.设计互斥算法时总是会面临一种情况,即没有获得锁的进程怎么办?通常有2种处理方式.一种是没有获得锁的调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,这就是自旋锁,他不用将县城阻塞起来(NON

并发编程--CAS自旋锁

在前两篇博客中我们介绍了并发编程--volatile应用与原理和并发编程--synchronized的实现原理(二),接下来我们介绍一下CAS自旋锁相关的知识. 一.自旋锁提出的背景 由于在多处理器系统环境中有些资源因为其有限性,有时需要互斥访问(mutual exclusion),这时会引入锁的机制,只有获取了锁的进程才能获取资源访问.即是每次只能有且只有一个进程能获取锁,才能进入自己的临界区,同一时间不能两个或两个以上进程进入临界区,当退出临界区时释放锁.设计互斥算法时总是会面临一种情况,即

SpinLock 自旋锁, CAS操作(Compare &amp; Set) ABA Problem

SpinLock 自旋锁 spinlock 用于CPU同步, 它的实现是基于CPU锁定数据总线的指令. 当某个CPU锁住数据总线后, 它读一个内存单元(spinlock_t)来判断这个spinlock 是否已经被别的CPU锁住. 如果否, 它写进一个特定值, 表示锁定成功, 然后返回. 如果是, 它会重复以上操作直到成功, 或者spin次数超过一个设定值. 锁定数据总线的指令只能保证一个机器指令内, CPU独占数据总线. 单CPU当然能用spinlock, 但实现上无需锁定数据总线. spinl