写点什么

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

作者:poemyang
  • 2025-09-27
    北京
  • 本文字数:2592 字

    阅读完需:约 9 分钟

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


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



探索 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 并发包中高层类的实现,构建了一个层次分明、功能强大的并发编程框架。


未完待续


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

发布于: 刚刚阅读数: 3
用户头像

poemyang

关注

让世界知道我的存在 2018-03-05 加入

技术/人文, 互联网

评论

发布
暂无评论
揭秘JUC:volatile与CAS,并发编程的两大基石_并发编程_poemyang_InfoQ写作社区