找回密码
 立即注册
首页 业界区 业界 揭秘JUC:volatile与CAS,并发编程的两大基石 ...

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

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

探索JUC并发包,会发现它与Java内存模型中的happens-before原则及内存语义紧密相连。从高层视角俯瞰,volatile关键字与CAS(Compare-And-Swap)操作构成了JUC并发包底层实现的核心基石。接下来,以并发工具Lock为例,剖析其背后的实现机制。
  1. class LockExample {
  2.     int x = 0;
  3.     Lock lock = new ReentrantLock();
  4.     public void set() {
  5.         // 获取锁
  6.         lock.lock();      
  7.         try {
  8.            x = 1;
  9.         } finally {
  10.             // 释放锁
  11.             lock.unlock();  
  12.         }
  13.     }
  14.     public void get() {
  15.         // 获取锁
  16.        lock.lock();        
  17.         try {
  18.            int i = x;
  19.            // ......
  20.         } finally {
  21.             // 释放锁
  22.            lock.unlock();   
  23.         }
  24.     }
  25. }
复制代码
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,则表示锁已完全释放,同时清除锁的持有者信息。
  1. // 关键volatile变量
  2. private volatile int state;
  3. protected final boolean tryAcquire(int acquires) {
  4.     // 1 获取到当前线程
  5.     final Thread current = Thread.currentThread();
  6.     // 2 获取到当前锁的state值
  7.     int c = getState();
  8.     // 3 如果state值为0,则是无线程占用锁
  9.     if (c == 0) {
  10.         // 4 compareAndSetState则通过CAS对state进行设置为1
  11.         if (compareAndSetState(0, acquires)) {
  12.             // 5 设置占用线程为当前线程并返回true
  13.             setExclusiveOwnerThread(current);
  14.             return true;
  15.         }
  16.     }
  17.     // 6 如果state不为0,并且当前线程等于锁占用的线程,则说明锁重入了。
  18.     else if (current == getExclusiveOwnerThread()) {
  19.         // 7 直接将state设置为+1
  20.         int nextc = c + acquires;
  21.         if (nextc < 0)
  22.             throw new Error("Maximum lock count exceeded");
  23.         setState(nextc);
  24.         return true;
  25.     }
  26.     // 8 如果是false,则说明是其他线程,直接返回false。
  27.     return false;
  28. }
  29. protected final boolean tryRelease(int releases) {
  30.    // 1 对state进行减值
  31.    int c = getState() - releases;
  32.    // 2 判断当前线程等于锁占用的线程
  33.    if (Thread.currentThread() != getExclusiveOwnerThread())
  34.         throw new IllegalMonitorStateException();
  35.    boolean free = false;
  36.     // 3 当c值为0,代表释放锁成功
  37.     if (c == 0) {
  38.         free = true;
  39.         // 4 设置为当前锁没有线程独占
  40.         setExclusiveOwnerThread(null);
  41.     }
  42.     // 5 将state重新置为0,意味其他线程可以重新抢锁
  43.     setState(c);
  44.     // 6 释放锁成功
  45.     return free;
  46. }
复制代码
从上述代码中,可以观察到volatile变量state在锁获取与释放过程中的关键作用。根据volatile的happens-before规则,释放锁的线程在修改volatile变量之前对共享变量的修改,对于后续获取该锁的线程来说是可见的。这确保了锁机制的正确性与线程间的数据一致性。
为了更直观地理解Lock的获取与释放过程,我们可以将其简化为如下伪代码。
  1. class SimplifiedLockExample {
  2.     int x = 0;
  3.     volatile int state;
  4.       
  5.     public void set() {
  6.         // 当前线程从主内存读取state值
  7.         while(state != 0) {
  8.            // 伪代码 阻塞当前线程
  9.            park(Thread.currentThread())
  10.         }
  11.         // CAS操作,确保只有一个线程能成功设置state为1
  12.        compareAndSwap(state, 1)
  13.        // 赋值操作,受volatile内存语义保护,防止重排序
  14.        x = 1;
  15.        // 释放锁,将state重置为0
  16.        state = 0;  
  17.        // 唤醒其他等待线程
  18.        unpark(nonCurrentThread());
  19.     }
  20.         
  21.     public void get() {
  22.        // 当前线程从主内存读取state值
  23.         while(state != 0) {
  24.            // 阻塞当前线程,等待锁释放
  25.            park(Thread.currentThread())
  26.         }         
  27.         // CAS操作,尝试获取锁
  28.         compareAndSwap(state, 1)
  29.         // 读取共享变量x的最新值
  30.         int i = x;
  31.         // 其他操作...
  32.         // 释放锁,将state重置为0
  33.         state = 0;
  34.         // 唤醒其他等待线程
  35.         unpark(nonCurrentThread());   
  36.      }
  37.      
  38.     // 伪代码方法,实际实现需依赖底层系统调用
  39.     private void park(Thread thread)
  40.     private void unpark(Thread thread)
  41.     private boolean compareAndSwap(int expect, int newValue, int updateValue)
  42.     private Thread nonCurrentThread()
  43. }
复制代码
Java的CAS会使用现代处理器上提供的原子指令,实现无锁的线程安全更新机制。同时,volatile变量的读/写可以实现线程线程之间的通信。如果仔细分析JUC并发包的源代码实现,会发现一个通用化的实现模式。
‌ 1)声明共享变量为volatile‌:确保变量的可见性与有序性。
‌ 2)使用CAS的原子条件更新‌:实现线程间的同步与数据的一致性更新。
‌ 3)配合volatile的读/写内存语义‌:实现线程间的通信与数据传递。
这一模式在AQS、非阻塞数据结构(如ConcurrentHashMap)及原子变量类(如AtomicInteger)等JUC并发包的基础类中得到了广泛应用。而这些基础类又进一步支撑了JUC并发包中高层类的实现,构建了一个层次分明、功能强大的并发编程框架。
未完待续
很高兴与你相遇!如果你喜欢本文内容,记得关注哦

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册