JDK Concurrent包组件概解(2)- 无锁并发的核心AbstractQueuedSynchronizer

本文深入探讨了Java并发编程中AbstractQueuedSynchronizer(AQS)的工作原理,包括其在ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等同步工具中的实现。AQS通过内部节点状态管理和线程挂起与唤醒来实现线程间的同步。文章详细阐述了AQS的公平与非公平策略,以及ConditionObject的等待与唤醒机制。此外,还介绍了CyclicBarrier的使用及其内部实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

AbstractQueuedSynchronizer

ReentrantLock独占锁

 ConditionObject

ReentrantReadWriteLock

Semaphore、CountDownLatch实现

Semaphore

CountDownLatch

CyclicBarrier


AbstractQueuedSynchronizer

CLH(Craig, Landin, and Hagersten locks): 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地自旋,不断轮询前驱的状态,如果前驱释放了锁就结束自旋。

用内部类Node来封装因等待权限而挂起的线程,维护一个双向链表。 使用LockSupport#unpark、LockSupport#park 来控制线程的挂起和再次执行。

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;
        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /** waitStatus value to indicate the next acquireShared should unconditionally propagate  */
        static final int PROPAGATE = -3;
        // 节点状态
        volatile int waitStatus;
        // 表示的线程
        volatile Thread thread;

Node的状态

  1. 添加到AQS链上时: 初始状态为0; 后面新添加挂起线程的节点时被更新为: SIGNAL (-1),标识有next在等待prev释放锁 。
  2. 大于0 只能是取消 CANCELLED (1) ;
  3. Node在“ConditionWaiter”链上才是 CONDITION  (-2)。
  4. PROPAGATE (-3) 从当前测试情况来看,仅是一个过渡状态。

Node的类型

  • 独占模式 EXCLUSIVE :  ReentrantLock、ReentrantReadWriteLock#WriteLock ;

关键方法:  AbstractQueuedSynchronizer#acquireQueued

方法#tryAcquire由具体组件来差异实现,决定当前线程是否可以拿到权限。

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
               // 前节点是head<前一个占锁运行的线程,现在已经释放了锁并唤醒当前线程> && 能拿到权限
                    setHead(node); // 将自己设置为head
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) // park线程
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

  • 共享模式 SHARED :  Semaphore、CountDownLatch、ReentrantReadWriteLock#ReadLock;

关键方法: AbstractQueuedSynchronizer#doAcquireShared

方法#tryAcquireShared也是由具体组件来差异实现,决定当前线程是否可以拿到权限。同时,这里还有一个#setHeadAndPropagate方法来控制向后传播唤醒next。

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg); // 决定当前线程是否可以获取权限,并向后唤醒
                    if (r >= 0) {
                        setHeadAndPropagate(node, r); // 向后传播唤醒next
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())  // park 线程
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

ReentrantLock独占锁

主要起作用的是它内部持有的内部类Sync变量,该内部对象实现了抽象类AbstractQueuedSynchronizer

实现分为:分公平锁FairSync、非公平锁NonfairSync。

  1. 公平锁:    优先判定 已经有其他等待线程在链头 则入链尾。
  2. 非公平锁: 直接compareAndSetState判定是否可以加锁。

大概逻辑如下 (见:AbstractQueuedSynchronizer#acquireQueued)

  1.  内部维护了一个"int state", 标识被同一个线程占用了多少次,每次占用 ++ ,释放 -- ,占用N次需释放N次。
  2.  父类AbstractOwnableSynchronizer里有"Thread exclusiveOwnerThread"来记录独占<NODE.EXCLUSIVE>线程,同线程可重入。
  3. 如果锁已经被其他线程占用,该线程被封装为Node,状态初始值为0,以compareAndSetTail方式入链尾,随后挂起该线程 ;同时将AQS链它前面的线程节点状态更新为Node.SIGNAL。
  4. 能获取到锁权限: 
    1. 当前执行线程(pre_node),它在链上此时一定是链头head。它释放锁权限后,节点状态重置为初始值0,唤醒链上next_node开始执行见:AbstractQueuedSynchronizer#unparkSuccessor
    2. 该next线程获取到锁权限后,就断开与pre_node的指针关系。

 

 线程节点在AQS链里: head.waitStatus \  head.next.waitStatus 都是 Node.SIGNAL ,只有最后tail节点还是初始值0。 

指定获取锁的超时时间  ReentrantLock#tryLock(long timeout, TimeUnit unit) 原理是:

 LockSupport#parkNanos(this, nanosTimeout)来约定挂起线程的时长 ,  超时后会再判定一次是否能获取锁,如果还是无法独占则返回false。

AbstractQueuedSynchronizer

 ConditionObject

它是AbstractQueuedSynchronizer的内部类,  单独维护了一个ConditionWaiter的节点链表。 

与ReentrantLock合用 类似于 synchronized 和 Object.wait() \ Object.notify() \ Object.notifyAll() 的组合。

先ConditionObject#await():

  1. 将当前持有锁权限的线程封装为Node, 状态是:Node.CONDITION ;链入到ConditionWaiter节点链末尾。
  2.  释放当前线程执行锁的权限, 如果有重入则需要全部释放,记录释放权限时的state值。 当前线程必须持有该锁的权限,否则抛出异常IllegalMonitorStateException(见:ReentrantLock.Sync#tryRelease)。
  3. 挂起该线程。
  4. 等到被 signal() | signalAll() 唤醒后,再重新去竞争锁权限,之前释放时的state值需要在重新获取权限时重设回来。

ConditionObject#awaitNanos也是使用LockSupport#parkNanos(this, nanosTimeout)来指定时间内挂起线程。

后 ConditionObject#signalAll() :(signal() 只处理第一个firstWaiter,处理过程一样。)

逐个遍历ConditionWaiter链上的所有节点

  1. 节点状态重置为0,重新链回到AQS等待链的末尾,并LockSupport#unpark 唤醒该线程(见: AbstractQueuedSynchronizer#transferForSignal)。 此时 ConditionObject#await()后半部分会开始执行。
  2. 同时,也断开该节点在ConditionWaiter链上的关系。

ReentrantReadWriteLock

  1. 同样也分公平与不公平策略, 可重入。写锁独占, 读读并行、读写和写写按队列顺序串行
  2. ReadLock 和 WriteLock 共用同一个Sync, 也就是说是在同一个AQS链上排队。
  3. state 高16位标识 read 的数量(SHARD), 低16位标识write的数量(EXCLUSIVE)。
  4. 写锁可以获取读锁,但读锁不能获取写锁!

Semaphore、CountDownLatch实现

Semaphore、CountDownLatch 里有它们自己的内部类Sync的实现,也是继承自抽象父类 AbstractQueuedSynchronizer 。  不一样的是:

  1. 它俩将等待线程封装成 Node 类型是 SHARED 。
  2. 它两是非独占的 , 当 state 满足条件时,是可以依次传导向后唤醒next节点线程。

Semaphore

内部类Sync也分公平FairSync、非公平NonfairSync,区别也是: 公平策略优先判定AQS链表已经有等待线程节点了,则先compareAndSetTail入链尾。

  1. 创建时 state设定初值N。

  2. acquire(m)后state都减m;直到state无法满足需要信号量个数时, 创建等待节点入AQS链并挂起线程。

  3. release(m)时,给state加m,接着唤醒next_node来拿信号量; next线程被唤醒后判定信号量是否满足:

    1. 不满足:  再次挂起。

    2. 满足:  state再减去m,如果剩余的state>=0,  该next线程将唤醒next.next ;循环这个判定过程直到state无法满足时的那个线程会再次挂起。(见 AbstractQueuedSynchronizer#doAcquireShared -> #setHeadAndPropagate -> #doReleaseShared)

CountDownLatch

内部类Sync是 static final。

  1. 创建时state设定初值N。

  2. A线程执行await() 时: 判定 state != 0 则创建等待节点入AQS链并挂起线程。

  3. 每一次的子线程执行countDown()时: state--,  再判定若已经全部释放(state == 0) 则next_wakeup_next依次传导向下唤醒(也就是说这里会唤醒A线程)。

CyclicBarrier

private static class Generation {  boolean broken = false; }

    /** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();
    private final int parties; // 屏障数
    private Generation generation = new Generation(); // 当前分代

    private int count; // 每一个分代都会重置为parties , 每个线程执行到await()时 count--
 
   public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties; 
        this.count = parties; 
        this.barrierCommand = barrierAction;
    }
   // 换代
   private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }
    /**  Sets current barrier generation as broken and wakes up everyone. Called only while holding lock. */
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

栅栏的实现有所不同: 直接使用了ReentrantLock加锁控制并发,维护计数值count的递减 。

 每个线程执行await()时:

  1. 先加锁 ,count --;
  2. 判定count != 0 时,表明还有其他线程没执行到栅栏, Condition#await() 挂起线程并释放锁;
  3. 直到客观上最后一个线程执行await()时, count将减至0,Condition.signalAll()唤醒所有挂起线程继续执行。
  4. CyclicBarrier是可以复用的,此时会重置进入nextGeneration 。
CyclicBarrier#dowait(boolean timed, long nanos)

未完,待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值