百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

注意,不能错过的CAS+volatile实现同步代码块

yuyutoo 2025-05-02 22:16 4 浏览 0 评论

前言:

最近看到有人说可以使用 CAS + volatile 实现同步代码块。

心想,确实是可以实现的呀!因为
AbstractQueuedSynchronizer(简称 AQS)内部就是通过 CAS + volatile(修饰同步标志位state) 实现的同步代码块。

并且ReentrantLock就是基于AQS原理来实现同步代码块的;ReentrantLock源码学习和了解AQS原理可以参考:带你探索ReentrantLock源码的快乐

今天,咱们就通过 CAS + volatile 实现一个 迷你版的AQS ;通过这个迷你版的AQS可以使大家对AQS原理更加清晰。

本文主线

  • CAS操作和volatile简述
  • CAS + volatile = 同步代码块(代码实现)
  • CAS操作和volatile简述:

    通过了解CAS操作和volatile来聊聊为什么使用它们实现同步代码块。

    CAS操作:

    CAS是什么?

    CAS是compare and swap的缩写,从字面上理解就是比较并更新;主要是通过 处理器的指令 来保证操作的原子性 。

    CAS 操作包含三个操作数:

  • 内存位置(V)
  • 预期原值(A)
  • 更新值(B)
  • 简单来说:从内存位置V上取到存储的值,将值和预期值A进行比较,如果值和预期值A的结果相等,那么我们就把新值B更新到内存位置V上,如果不相等,那么就重复上述操作直到成功为止。

    例如:JDK中的 unsafe 类中的 compareAndSwapInt 方法:

    unsafe.compareAndSwapInt(this, stateOffset, expect, update);
  • stateOffset 变量值在内存中存放的位置;
  • expect 期望值;
  • update 更新值;
  • CAS的优点:

    CAS是一种无锁化编程,是一种非阻塞的轻量级的乐观锁;相比于synchronized阻塞式的重量级的悲观锁来说,性能会好很多 。

    但是注意:synchronized关键字在不断地优化下(锁升级优化等),性能也变得十分的好。

    volatile 关键字:

    volatile是什么?

    volatile是java虚拟机提供的一种轻量级同步机制。

    volatile的作用:

  • 可以保证被volatile修饰的变量的读写具有原子性,不保证复合操作(i++操作等)的原子性;
  • 禁止指令重排序;
  • 被volatile修饰的的变量修改后,可以马上被其它线程感知到,保证可见性;
  • 通过了解CAS操作和volatile关键字后,才可以更加清晰地理解下面实现的同步代码的demo程序。

    CAS + volatile = 同步代码块

    总述同步代码块的实现原理:

    1. 使用 volatile 关键字修饰一个int类型的同步标志位state,初始值为0;
    2. 加锁/释放锁时使用CAS操作对同步标志位state进行更新; 加锁成功,同步标志位值为 1,加锁状态; 释放锁成功,同步标志位值为0,初始状态;

    加锁实现:

    加锁流程图:

    加锁代码:

     **
     * 加锁,非公平方式获取锁
     */
    public final void lock() {
      
        while (true) {
            // CAS操作更新同步标志位
            if (compareAndSetState(0, 1)) {
                // 将独占锁的拥有者设置为当前线程
                exclusiveOwnerThread = Thread.currentThread();
    
                System.out.println(Thread.currentThread() + "  lock success ! set lock owner is current thread .  " +
                        "state:" + state);
    
                try {
                    // 睡眠一小会,模拟更加好的效果
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 跳出循环
                break;
    
            } else {
                // TODO 如果同步标志位是1,并且锁的拥有者是当前线程的话,则可以设置重入,但本方法暂未实现
                if (1 == state && Thread.currentThread() == exclusiveOwnerThread) {
                    // 进行设置重入锁
                }
    
                System.out.println(Thread.currentThread() + "  lock fail ! If the owner of the lock is the current thread," +
                        "  the reentrant lock needs to be set;else Adds the current thread to the blocking queue .");
    
                // 将线程阻塞,并将其放入阻塞列表
                parkThreadList.add(Thread.currentThread());
                LockSupport.park(this);
    
                // 线程被唤醒后会执行此处,并且继续执行此 while 循环
                System.out.println(Thread.currentThread() + "  The currently blocking thread is awakened !");
            }
        }
    }

    锁释放实现:

    释放锁流程图:

    释放锁代码:

    /**
     * 释放锁
     *
     * @return
     */
    public final boolean unlock() {
        // 判断锁的拥有者是否为当前线程
        if (Thread.currentThread() != exclusiveOwnerThread) {
            throw new IllegalMonitorStateException("Lock release failed !  The owner of the lock is not " +
                    "the current thread.");
        }
        // 将同步标志位设置为0,初始未加锁状态
        state = 0;
        // 将独占锁的拥有者设置为 null
        exclusiveOwnerThread = null;
    
        System.out.println(Thread.currentThread() + "  Release the lock successfully, and then wake up " +
                "the thread node in the blocking queue !  state:" + state);
    
        if (parkThreadList.size() > 0) {
            // 从阻塞列表中获取阻塞的线程
            Thread thread = parkThreadList.get(0);
            // 唤醒阻塞的线程
            LockSupport.unpark(thread);
            // 将唤醒的线程从阻塞列表中移除
            parkThreadList.remove(0);
        }
    
        return true;
    }

    完整代码如下:

    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.locks.LockSupport;
    
    
    /**
     * @PACKAGE_NAME: com.lyl.thread6
     * @ClassName: AqsUtil
     * @Description: 使用 CAS + volatile 同步标志位  =  实现 迷你版AQS ;
     * <p>
     * <p>
     * 注意:本类只简单实现了基本的非公平方式的独占锁的获取与释放; 像重入锁、公平方式获取锁、共享锁等都暂未实现
     * <p/>
     * @Date: 2021-01-15 10:55
     * @Author: [ 木子雷 ] 公众号
     **/
    public class AqsUtil {
    
        /**
         * 同步标志位
         */
        private volatile int state = 0;
    
        /**
         * 独占锁拥有者
         */
        private transient Thread exclusiveOwnerThread;
    
        /**
         * JDK中的rt.jar中的Unsafe类提供了硬件级别的原子性操作
         */
        private static final Unsafe unsafe;
    
        /**
         * 存放阻塞线程的列表
         */
        private static List<Thread> parkThreadList = new ArrayList<>();
    
        /**
         * 同步标志位 的“起始地址”偏移量
         */
        private static final long stateOffset;
    
    
        static {
            try {
                unsafe = getUnsafe();
                // 获取 同步标志位status 的“起始地址”偏移量
                stateOffset = unsafe.objectFieldOffset(AqsUtil.class.getDeclaredField("state"));
            } catch (NoSuchFieldException e) {
                throw new Error(e);
            }
        }
    
    
        /**
         * 通过反射 获取 Unsafe 对象
         *
         * @return
         */
        private static Unsafe getUnsafe() {
            try {
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                return (Unsafe) field.get(null);
            } catch (Exception e) {
                return null;
            }
        }
    
    
        /**
         * 加锁,非公平方式获取锁
         */
        public final void lock() {
            
            while (true) {
              
                if (compareAndSetState(0, 1)) {
                    // 将独占锁的拥有者设置为当前线程
                    exclusiveOwnerThread = Thread.currentThread();
    
                    System.out.println(Thread.currentThread() + "  lock success ! set lock owner is current thread .  " +
                            "state:" + state);
    
                    try {
                        // 睡眠一小会,模拟更加好的效果
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    // 跳出循环
                    break;
    
                } else {
                    // TODO 如果同步标志位是1,并且锁的拥有者是当前线程的话,则可以设置重入,但本方法暂未实现
                    if (1 == state && Thread.currentThread() == exclusiveOwnerThread) {
                        // 进行设置重入锁
                    }
    
                    System.out.println(Thread.currentThread() + "  lock fail ! If the owner of the lock is the current thread," +
                            "  the reentrant lock needs to be set;else Adds the current thread to the blocking queue .");
    
                    // 将线程阻塞,并将其放入阻塞队列
                    parkThreadList.add(Thread.currentThread());
                    LockSupport.park(this);
    
                    // 线程被唤醒后会执行此处,并且继续执行此 while 循环
                    System.out.println(Thread.currentThread() + "  The currently blocking thread is awakened !");
                }
            }
        }
    
    
        /**
         * 释放锁
         *
         * @return
         */
        public final boolean unlock() {
            if (Thread.currentThread() != exclusiveOwnerThread) {
                throw new IllegalMonitorStateException("Lock release failed !  The owner of the lock is not " +
                        "the current thread.");
            }
            // 将同步标志位设置为0,初始未加锁状态
            state = 0;
            // 将独占锁的拥有者设置为 null
            exclusiveOwnerThread = null;
    
            System.out.println(Thread.currentThread() + "  Release the lock successfully, and then wake up " +
                    "the thread node in the blocking queue !  state:" + state);
    
            if (parkThreadList.size() > 0) {
                // 从阻塞列表中获取阻塞的线程
                Thread thread = parkThreadList.get(0);
                // 唤醒阻塞的线程
                LockSupport.unpark(thread);
                // 将唤醒的线程从阻塞列表中移除
                parkThreadList.remove(0);
            }
    
            return true;
        }
    
    
        /**
         * 使用CAS 安全的更新 同步标志位
         *
         * @param expect
         * @param update
         * @return
         */
        public final boolean compareAndSetState(int expect, int update) {
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }
    
    }

    测试运行:

    测试代码:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * @PACKAGE_NAME: com.lyl.thread6
     * @ClassName: SynCodeBlock
     * @Description: 简单的测试
     * @Date: 2021-01-15 10:26
     * @Author: [ 木子雷 ] 公众号
     **/
    public class SynCodeBlock {
    
    
        public static void main(String[] args) {
            // 10 个线程的固定线程池
            ExecutorService logWorkerThreadPool = Executors.newFixedThreadPool(10);
    
            AqsUtil aqsUtil = new AqsUtil();
    
            int i = 10;
            while (i > 0) {
                logWorkerThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        test(aqsUtil);
                    }
                });
                --i;
            }
        }
    
    
        public static void test(AqsUtil aqsUtil) {
    
            // 加锁
            aqsUtil.lock();
    
            try {
                System.out.println("正常的业务处理");
            } finally {
                // 释放锁
                aqsUtil.unlock();
            }
        }
    
    }

    运行结果:

    例如上面测试程序启动了10个线程同时执行同步代码块,可能此时只有线程 thread-2 获取到了锁,其余线程由于没有获取到锁被阻塞进入到了阻塞列表中;

    当获取锁的线程释放了锁后,会唤醒阻塞列表中的线程,并且是按照进入列表的顺序被唤醒;此时被唤醒的线程会再次去尝试获取锁,如果此时有新线程同时尝试获取锁,那么此时也存在竞争了,这就是非公平方式抢占锁(不会按照申请锁的顺序获取锁)。

    扩展:

    上面的代码中没有实现线程自旋操作,下面看看该怎么实现呢?

    首先说说为什么需要自旋操作:

    因为在某些场景下,同步资源的锁定时间很短,如果没有获取到锁的线程,为了这点时间就进行阻塞的话,就有些得不偿失了;因为进入阻塞时会进行线程上下文的切换,这个消耗是很大的;

    使线程进行自旋的话就很大可能会避免阻塞时的线程上下文切换的消耗;并且一般情况下都会设置一个线程自旋的次数,超过这个次数后,线程还未获取到锁的话,也要将其阻塞了,防止线程一直自旋下去白白浪费CPU资源。

    代码如下:

    作者:木子雷
    链接:
    https://juejin.cn/post/6919342709886959624

    来源:掘金

    相关推荐

    对volatile,synchronized,AQS的加锁解锁原理的一些理解

    一、为什么要加锁,要实现同步多线程编程中,有可能会出现多个线程同时访问同一个共享、可变资源的情况,这个资源我们称之其为临界资源;这种资源可能是:对象、变量、文件等。...

    注意,不能错过的CAS+volatile实现同步代码块

    前言:最近看到有人说可以使用CAS+volatile实现同步代码块。心想,确实是可以实现的呀!因为AbstractQueuedSynchronizer(简称AQS)内部就是通过CAS+...

    面试并发volatile关键字时,我们应该具备哪些谈资?

    提前发现更多精彩内容,请访问https://dayarch.top/提前发现更多精彩内容,请访问https://dayarch.top/提前发现更多精彩内容,请访问https://dayarch...

    无锁同步-JAVA之Volatile、Atomic和CAS

    1、概要本文是无锁同步系列文章的第二篇,主要探讨JAVA中的原子操作,以及如何进行无锁同步。关于JAVA中的原子操作,我们很容易想到的是Volatile变量、java.util.concurrent....

    C/C++面试题(二):std::atomic与volatile

    volatile是C/C++中的一个关键字,用于告知编译器某个变量的值可能会在程序的控制之外被意外修改(例如被硬件、中断服务程序、多线程环境或其他外部代理)。为了防止编译器对代码进行某些可能破坏...

    VOCs(Volatile Organic Compounds)挥发性有机化合物及测试方法

    经常看到一些三防漆、涂料、油漆类产品的介绍中提到VOC、VOCs等概念,那么什么是VOC、VOCs和TVOC,VOCs主要包括哪些物质?VOCs的来源有哪些?VOCs的危害及国家标准是什么?一、V...

    对volatile 及happen—before的理解

    happen—before规则介绍Java...

    这一篇我们来了解Synchronized、Volatile、Final关键字

    题外话:蓝银王觉醒了!!--来自于一个斗罗大陆动漫爱好者(鹅,打钱!)湿兄这两天回家了,办了点大事,回来的时候我弟弟还舍不得我,哭着不愿意让我回京(我弟还是小学),我也心里很不舍,但是还是要回京奋斗...

    关于 Java 关键字 volatile 的总结

    1什么是volatilevolatile是Java的一个关键字,它提供了一种轻量级的同步机制。相比于重量级锁synchronized,volatile更为轻量级,因为它不会引起线程上下文...

    大白话聊聊Java并发面试问题之volatile到底是什么?

    用最简单的大白话,加上多张图给大家说一下,volatile到底是什么?...

    为什么要有volatile关键字(volatile 关键字为什么不能保证原子性)

    在嵌入式编程和多线程编程中,我们常会见到volatile关键字声明的变量。下面说一下volatile关键字的作用:1.保持变量内存可见简而言之就是用volatile声明的变量会告诉编译器和处理器,这个...

    Java的volatile到底怎么理解?(java volatitle)

    我们都知道,在Java中有很多的关键字,比如synchronize比如volatile,这些都是一些比较关键的,还有final,今天我们就来聊一下这个volatile因为这个vo...

    Java多线程编程中的volatile关键字:解密神秘的共享内存

    Java多线程编程中的volatile关键字:解密神秘的共享内存在Java多线程编程的世界里,volatile关键字就像一位低调却至关重要的守护者。它默默无闻地站岗放哨,确保多个线程之间能够正确地共享...

    你了解volatile关键字的作用吗?(关键字volatile有什么含意?并举出三个不同的例子?)

    【死记硬背】volatile关键字主要用于保持内存的变量可见性和禁止重排序。变量可见性:当一个线程改变了变量的值,那么新的值对于其他线程也是可以立即获取到的。禁止重排序:...

    谈谈你对volatile 关键字作用和原理的理解

    一位6年工作经验的小伙伴,在某里二面的时候被问到“volatile”关键字。然后,就没有然后了…同样,还有一位4年的小伙伴,去某团面试也被问到“volatile关键字“。然后,也没有然后了…...

    取消回复欢迎 发表评论: