什么是 CAS(自旋锁)? 它的优缺点? 如何使用CAS实现一把锁?

简介: 该博客文章解释了什么是CAS(自旋锁),包括CAS的基本概念、实现原理、优缺点,以及如何使用CAS实现锁的逻辑,并提供了使用CAS实现锁的Java完整代码示例和测试结果。

什么是自旋锁?

CAS
没有获取到锁的线程是不会阻塞的,通过循环控制一直不断的获取锁。

CAS: Compare and Swap,翻译成比较并交换。 执行函数 CAS(V,E,N)

CAS 有 3 个操作数,内存值 V,旧的预期值 E,要修改的新值 N。当且仅当预期值 E 和内存值 V 相同时,将内存值 V 修改为 N,否则什么都不做

在这里插入图片描述

  1. Cas 是通过硬件指令,保证原子性
  2. Java 是通过 unsafe jni 技术

原子类: AtomicBoolean,AtomicInteger,AtomicLong 等使用 CAS 实现。

优缺点

优点:没有获取到锁的线程,会一直在用户态,不会阻塞,没有锁的线程会一直通过循环控制重试。

缺点:通过死循环控制,消耗 cpu 资源比较高,需要控制循次数,避免 cpu 飙高问题。

CAS实现一把锁逻辑

Cas 无锁机制原理:

  1. 定义一个锁的状态;
  2. 状态状态值=0 则表示没有线程获取到该锁;
  3. 状态状态值=1 则表示有线程已经持有该锁;

实现细节:
CAS 获取锁:
将该锁的状态从 0 改为 1-----能够修改成功 cas 成功则表示获取锁成功
如果获取锁失败–修改失败,则不会阻塞而是通过循环(自旋来控制重试)

CAS 释放锁:
将该锁的状态从 1 改为 0 如果能够改成功 cas 成功则表示释放锁成

完整代码

/**
 * @author zyz
 * @version 1.0
 * @data 2023/7/17 15:09
 * @Description: 使用CAS实现一把锁
 *               测试
 *                  1、上锁不使用循环实现
 *                     1):获取锁、释放锁, 结果有些获取成功,有些获取失败
 *                     2):获取锁,不释放锁  结果:只有一个线程获取锁成功,其它都失败
 *                  2、上锁使用循环实现
 *                     1) 获取锁、释放锁, 全部成功。
 *                     2) 获取锁,不释放锁, 结果:只有一个线程获取成功,其它线程一直循环等待。CPU飙高
 */
public class UsingCasHandwritingLock {
    private AtomicLong cas = new AtomicLong(0);
    private Thread lockCurrentThread;

    /**
     * 获取锁
     * 锁是有状态的
     * 0 --- 表示没有人持有该锁
     * 1 --- 表示该锁已经被线程持有
     * 获取成功:cas 0变为1 cas = true
     * 获取锁失败 cas false
     */
    public Boolean tryLock() {
        boolean result = cas.compareAndSet(0, 1);
        return result;
    }

    /**
     *:优点:没有获取到锁的线程,会一直在用户态,不会阻塞,没有锁的线程会一直通过循环控制重试。
     * 缺点:通过死循环控制,消耗 cpu 资源比较高,需要控制循次数,避免 cpu 飙高问题;
     * @return
     */
//    public Boolean tryLock(){
//        while (true){
//            System.out.println(Thread.currentThread().getName() + ",CAS");
//            boolean result = cas.compareAndSet(0,1);
//            if(result){
//                lockCurrentThread = Thread.currentThread();
//                return true;
//            }
//        }
//    }

    /**
     * 释放锁
     */
    public Boolean unLock() {
        if (lockCurrentThread != Thread.currentThread()) {
            return false;
        }
        return cas.compareAndSet(1, 0);

    }

    public static void main(String[] args) {
        UsingCasHandwritingLock lock = new UsingCasHandwritingLock();
        IntStream.range(1, 10).forEach((i) -> new Thread(() -> {
            try {
                boolean result = lock.tryLock();
                if (result) {
                    lock.lockCurrentThread = Thread.currentThread();
                    System.out.println(Thread.currentThread().getName() + ",获取锁成功~~~");
                } else {
                    System.out.println(Thread.currentThread().getName() + ",获取锁失败~~~");
                }

            } catch (Exception ex) {

            } finally {
                if (lock != null) {
                    lock.unLock();
                }

            }
        }).start());
    }
}

测试结果

1、释放锁(非循环获取锁)

    //获取锁
    public Boolean tryLock() {
        boolean result = cas.compareAndSet(0, 1);
        return result;
    }

正常释放锁

   try {
   //做的任务
   } catch (Exception ex) {
   //抛异常
   } finally {
   //释放锁
       if (lock != null) {
           lock.unLock();
       }
   }

结果:有些线程获取锁成功,有些线程获取锁失败。

说明:线程获取到该锁,并且还在执行任务,未到释放锁的时候。其它线程在获取锁的时候,就会出现获取锁失败的情况。

在这里插入图片描述

2、不释放锁(非循环释放锁)

    //获取锁
    public Boolean tryLock() {
        boolean result = cas.compareAndSet(0, 1);
        return result;
    }

将释放锁的过程取消,获取到该锁的线程一直拿着。

   try {
   //做的任务
   } catch (Exception ex) {
   //抛异常
   } finally {
   //释放锁
   //    if (lock != null) {
   //        lock.unLock();
   //    }
   }

结果:只有一个线程获取锁成功,其它线程获取锁均失败

说明:某个线程获取到该锁,并且一直拿着,不释放。其它线程就不能获取到锁。

在这里插入图片描述

3、释放锁(循环获取锁)

    public Boolean tryLock(){
        while (true){
            System.out.println(Thread.currentThread().getName() + ",CAS");
            boolean result = cas.compareAndSet(0,1);
            if(result){
                lockCurrentThread = Thread.currentThread();
                return true;
            }
        }
    }

当前线程执行完任务后,释放锁

   try {
   //做的任务
   } catch (Exception ex) {
   //抛异常
   } finally {
   //释放锁
       if (lock != null) {
           lock.unLock();
       }
   }

结果:线程全部获取到锁。

说明:未获取到锁的线程,通过循环的形式一直尝试获取锁。只要有线程释放了锁。其它线程就去尝试获取锁,获取不到锁的线程,就一直循环尝试获取。直到获取到锁为止。

在这里插入图片描述

4、不释放锁 (循环获取锁)

    public Boolean tryLock(){
        while (true){
            System.out.println(Thread.currentThread().getName() + ",CAS");
            boolean result = cas.compareAndSet(0,1);
            if(result){
                lockCurrentThread = Thread.currentThread();
                return true;
            }
        }
    }

线程执行完任务后,不释放锁

   try {
   //做的任务
   } catch (Exception ex) {
   //抛异常
   } finally {
   //释放锁
   //    if (lock != null) {
   //        lock.unLock();
   //    }
   }

效果:只有一个线程获取到锁,其它线程一直循环尝试获取锁。
说明:当一个线程获取到锁之后,其它线程就一直循环尝试获取锁。但是该线程没有释放锁,就会导致其它线程一直循环尝试获取锁,就会导致CPU飙高。
在这里插入图片描述

在这里插入图片描述

相关文章
|
6月前
|
应用服务中间件 Linux 调度
锁和原子操作CAS的底层实现
锁和原子操作CAS的底层实现
53 0
|
5月前
|
Java Linux
如何实现无锁并发:深入理解CAS原理
如何实现无锁并发:深入理解CAS原理
191 2
|
11月前
|
API 调度 C语言
互斥锁,自旋锁,原子操作的原理,区别和实现
v互斥锁,自旋锁,原子操作的原理,区别和实现
121 0
|
安全 算法 Java
可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用
可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用
|
6月前
|
安全 Java
大厂面试题详解:synchronized的偏向锁和自旋锁怎么实现的
字节跳动大厂面试题详解:synchronized的偏向锁和自旋锁怎么实现的
71 0
|
6月前
|
存储 安全 中间件
锁与原子操作CAS的底层实现
锁与原子操作CAS的底层实现
|
6月前
|
存储 缓存 编译器
C++11及上的原子操作底层原理与锁实现
C++11及上的原子操作底层原理与锁实现
408 0
|
12月前
|
算法
互斥锁原理
互斥锁原理
94 0
|
安全 Java 编译器
【JavaEE】多线程进阶问题-锁策略and死锁,CAS操作,Synchronized原理
JavaEE & 多线程进阶问题 & 锁策略and 死锁,CAS操作,Synchronized原理
59 0
|
存储 安全 Java
synchronize偏向锁底层实现原理
无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。
71 0