揭秘JUC:volatile与CAS,并发编程的两大基石

简介: JUC并发包由Doug Lea打造,基于volatile与CAS实现线程安全,依托happens-before原则保障内存可见性,通过AQS框架构建锁与同步器,形成高效并发编程基石。

JUC(java.util.concurrent)并发包,作为Java语言并发编程的利器,由并发编程领域的泰斗道格·利(Doug Lea)精心打造。它提供了一系列高效、线程安全的工具类、接口及原子类,极大地简化了并发编程的开发流程与管理复杂度。

JUC并发包与happens-before、内存语义的关系
image.png

探索JUC并发包,会发现它与Java内存模型中的happens-before原则及内存语义紧密相连。从高层视角俯瞰,volatile关键字与CAS(Compare-And-Swap)操作构成了JUC并发包底层实现的核心基石。接下来,以并发工具Lock为例,剖析其背后的实现机制。

class LockExample {
   
    int x = 0;
    Lock lock = new ReentrantLock();

    public void set() {
   
        // 获取锁
        lock.lock();       
        try {
   
           x = 1;
        } finally {
   
            // 释放锁
            lock.unlock();  
        }
    }

    public void get() {
   
        // 获取锁
       lock.lock();        
        try {
   
           int i = x;
           // ......
        } finally {
   
            // 释放锁
           lock.unlock();   
        }
    }
}

Lock的实现依赖于Java同步器框架(AbstractQueuedSynchronizer,AQS)。AQS内部维护了一个由volatile修饰的整型变量state,用于表示同步状态。
‌ 1)获取锁‌:当调用Lock的lock()方法时,会触发AQS的tryAcquire()方法尝试获取锁。该方法首先检查当前state是否为0(表示锁未被占用),若是,则通过CAS操作将state设置为1,并标记当前线程为锁的持有者。若锁已被当前线程持有(即重入锁情况),则直接增加state的值。
‌ 2)释放锁‌:当调用Lock的unlock()方法时,会触发AQS的tryRelease()方法释放锁。该方法首先减少state的值,若减少后state为0,则表示锁已完全释放,同时清除锁的持有者信息。

// 关键volatile变量
private volatile int state;

protected final boolean tryAcquire(int acquires) {
   
    // 1 获取到当前线程
    final Thread current = Thread.currentThread();
    // 2 获取到当前锁的state值
    int c = getState();
    // 3 如果state值为0,则是无线程占用锁
    if (c == 0) {
   
        // 4 compareAndSetState则通过CAS对state进行设置为1
        if (compareAndSetState(0, acquires)) {
   
            // 5 设置占用线程为当前线程并返回true
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 6 如果state不为0,并且当前线程等于锁占用的线程,则说明锁重入了。
    else if (current == getExclusiveOwnerThread()) {
   
        // 7 直接将state设置为+1
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 8 如果是false,则说明是其他线程,直接返回false。
    return false;
}

protected final boolean tryRelease(int releases) {
   
   // 1 对state进行减值
   int c = getState() - releases;
   // 2 判断当前线程等于锁占用的线程
   if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
   boolean free = false;
    // 3 当c值为0,代表释放锁成功
    if (c == 0) {
   
        free = true;
        // 4 设置为当前锁没有线程独占
        setExclusiveOwnerThread(null);
    }
    // 5 将state重新置为0,意味其他线程可以重新抢锁
    setState(c);
    // 6 释放锁成功
    return free;
}

从上述代码中,可以观察到volatile变量state在锁获取与释放过程中的关键作用。根据volatile的happens-before规则,释放锁的线程在修改volatile变量之前对共享变量的修改,对于后续获取该锁的线程来说是可见的。这确保了锁机制的正确性与线程间的数据一致性。
为了更直观地理解Lock的获取与释放过程,我们可以将其简化为如下伪代码。

class SimplifiedLockExample {
   
    int x = 0;
    volatile int state;

    public void set() {
   
        // 当前线程从主内存读取state值
        while(state != 0) {
   
           // 伪代码 阻塞当前线程
           park(Thread.currentThread())
        } 
        // CAS操作,确保只有一个线程能成功设置state为1
       compareAndSwap(state, 1)
       // 赋值操作,受volatile内存语义保护,防止重排序
       x = 1;
       // 释放锁,将state重置为0
       state = 0;  
       // 唤醒其他等待线程
       unpark(nonCurrentThread());
    }

    public void get() {
   
       // 当前线程从主内存读取state值
        while(state != 0) {
   
           // 阻塞当前线程,等待锁释放
           park(Thread.currentThread())
        }          
        // CAS操作,尝试获取锁 
        compareAndSwap(state, 1)
        // 读取共享变量x的最新值
        int i = x;
        // 其他操作...
        // 释放锁,将state重置为0
        state = 0;
        // 唤醒其他等待线程
        unpark(nonCurrentThread());   
     }

    // 伪代码方法,实际实现需依赖底层系统调用
    private void park(Thread thread) 
    private void unpark(Thread thread) 
    private boolean compareAndSwap(int expect, int newValue, int updateValue) 
    private Thread nonCurrentThread()
}

Java的CAS会使用现代处理器上提供的原子指令,实现无锁的线程安全更新机制。同时,volatile变量的读/写可以实现线程线程之间的通信。如果仔细分析JUC并发包的源代码实现,会发现一个通用化的实现模式。
‌ 1)声明共享变量为volatile‌:确保变量的可见性与有序性。
‌ 2)使用CAS的原子条件更新‌:实现线程间的同步与数据的一致性更新。
‌ 3)配合volatile的读/写内存语义‌:实现线程间的通信与数据传递。
这一模式在AQS、非阻塞数据结构(如ConcurrentHashMap)及原子变量类(如AtomicInteger)等JUC并发包的基础类中得到了广泛应用。而这些基础类又进一步支撑了JUC并发包中高层类的实现,构建了一个层次分明、功能强大的并发编程框架。

未完待续

很高兴与你相遇!如果你喜欢本文内容,记得关注哦

目录
相关文章
|
存储 Java
HashMap扩容机制详解
HashMap扩容机制详解
|
6月前
|
设计模式 算法 安全
JUC系列之《深入理解AQS:Java并发锁的基石与灵魂 》
本文深入解析Java并发核心组件AQS(AbstractQueuedSynchronizer),从其设计动机、核心思想到源码实现,系统阐述了AQS如何通过state状态、CLH队列和模板方法模式构建通用同步框架,并结合独占与共享模式分析典型应用,最后通过自定义锁的实战案例,帮助读者掌握其原理与最佳实践。
|
6月前
|
SQL 人工智能 运维
一场由AI拯救的数据重构之战
本文以数据研发工程师小D的日常困境为切入点,探讨如何借助AI技术提升数据研发效率。通过构建“数研小助手”智能Agent,覆盖需求评估、模型评审、代码开发、运维排查等全链路环节,结合大模型能力与内部工具(如图治MCP、D2 API),实现影响分析、规范检查、代码优化与问题定位的自动化,系统性解决传统研发中耗时长、协作难、维护成本高等痛点,推动数据研发向智能化跃迁。
398 29
一场由AI拯救的数据重构之战
|
6月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
6月前
|
弹性计算 运维 安全
阿里云轻量应用服务器与云服务器ECS啥区别?新手帮助教程
阿里云轻量应用服务器适合个人开发者搭建博客、测试环境等低流量场景,操作简单、成本低;ECS适用于企业级高负载业务,功能强大、灵活可扩展。二者在性能、网络、镜像及运维管理上差异显著,用户应根据实际需求选择。
481 10
|
6月前
|
人工智能 运维 算法
AI来了,运维不慌:教你用人工智能把团队管理提速三倍!
AI来了,运维不慌:教你用人工智能把团队管理提速三倍!
739 8
|
6月前
|
测试技术
哪里不对改哪里!全能图像编辑模型Qwen-Image-Edit来啦
Qwen-Image-Edit基于20B Qwen-Image模型,融合视觉语义与外观控制,支持中英文文字精准编辑、风格迁移、IP创作等多重功能,具备SOTA性能,助力低门槛、高精度图像编辑。
3238 23
|
6月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
464 6
|
6月前
|
人工智能 安全 Serverless
再看 AI 网关:助力 AI 应用创新的关键基础设施
AI 网关作为云产品推出已有半年的时间,这半年的时间里,AI 网关从内核到外在都进行了大量的进化,本文将从 AI 网关的诞生、AI 网关的产品能力、AI 网关的开放生态,以及新推出的 Serverless 版,对其进行一个全面的介绍,期望对正在进行 AI 应用落地的朋友,在 AI 基础设施选型方面提供一些参考。
1123 86
|
6月前
|
人工智能 自然语言处理 安全
AutoGen框架入门:5个核心概念搭建智能体协作系统
AutoGen是微软开源的多智能体AI框架,支持多个AI智能体与人类协作,通过对话完成复杂任务。各智能体具备不同角色与能力,可调用工具、执行代码,并在群聊中辩论、推理、纠错,实现无需人工干预的自动化协作,适用于复杂问题求解与团队化AI应用开发。
732 13
AutoGen框架入门:5个核心概念搭建智能体协作系统

热门文章

最新文章