AQS系列

本文围绕Java的AQS展开,介绍其官方定义、重要属性及各类方法。还阐述了条件变量Condition,以及ReentrantLock、ReentrantReadWriteLock等同步器的实现、特点和示例。同时分析了Semaphore、CountDownLatch、cyclicBarrier等工具的使用及可能存在的问题。

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

1.AQS

·官方定义

  1. 提供一个框架,用于实现依赖先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等)。 该类被设计为大多数类型的同步器的有用依据,这些同步器依赖于单个原子int值来表示状态。 子类必须定义改变此状态的受保护方法,以及根据该对象被获取或释放来定义该状态的含义。 给定这些,这个类中的其他方法执行所有排队和阻塞机制。 子类可以保持其他状态字段,但只以原子方式更新int使用方法操纵值getState()setState(int)compareAndSetState(int, int)被跟踪相对于同步。

  2. 子类应定义为非公共内部助手类,用于实现其封闭类的同步属性。 AbstractQueuedSynchronizer类不实现任何同步接口。 相反,它定义了一些方法,如acquireInterruptibly(int) ,可以通过具体的锁和相关同步器来调用适当履行其公共方法。

  3. 此类支持默认独占模式和共享模式。 当以独占模式获取时,尝试通过其他线程获取不能成功。 多线程获取的共享模式可能(但不需要)成功。 除了在机械意义上,这个类不理解这些差异,当共享模式获取成功时,下一个等待线程(如果存在)也必须确定它是否也可以获取。 在不同模式下等待的线程共享相同的FIFO队列。 通常,实现子类只支持这些模式之一,但是两者都可以在ReadWriteLock中发挥作用 。 仅支持独占或仅共享模式的子类不需要定义支持未使用模式的方法。

  4. 这个类定义的嵌套AbstractQueuedSynchronizer.ConditionObject可用于作为一类Condition由子类支持独占模式用于该方法的实施isHeldExclusively()份报告是否同步排他相对于保持在当前线程,方法release(int)与当前调用getState()值完全释放此目的,和acquire(int) ,给定此保存的状态值,最终将此对象恢复到其先前获取的状态。 AbstractQueuedSynchronizer方法将创建此类条件,因此如果不能满足此约束,请勿使用该约束。 AbstractQueuedSynchronizer.ConditionObject的行为当然取决于其同步器实现的语义。

  5. 该类为内部队列提供检查,检测和监控方法,以及条件对象的类似方法。 这些可以根据需要导出到类中,使用AbstractQueuedSynchronizer进行同步机制。

  6. 此类的序列化仅存储底层原子整数维持状态,因此反序列化对象具有空线程队列。 需要可序列化的典型子类将定义一个readObject方法,可以将其恢复为readObject时的已知初始状态

·Node类

 static final class Node {
     static final Node SHARED = new Node();//标记Node处于共享状态
     static final Node EXCLUSIVE = null; //标记Node处于独占状态
     static final int CANCELLED =  1;//同步队列中线程被取消的状态
     static final int SIGNAL    = -1;//同步队列的当前节点后面的线程需要被唤醒
     static final int CONDITION = -2;//表明当前节点正在等待条件
     static final int PROPAGATE = -3;//表明唤醒后面节点的行为需要被传播(唤醒传播)
     volatile int waitStatus;//等待状态
     volatile Node prev;//前一个节点
     volatile Node next;//后一个节点
     volatile Thread thread;//通过这个来控制线程的行为
     Node nextWaiter;//条件队列中的下一个等待节点
     final boolean isShared() {//是否是共享状态
         return nextWaiter == SHARED;
     }
     final Node predecessor() throws NullPointerException {//获取前一个节点
         Node p = prev;
         if (p == null)
             throw new NullPointerException();
         else
             return p;
     }
     Node() {    // Used to establish initial head or SHARED marker
     }
     //用于条件等待队列中添加节点
     Node(Thread thread, Node mode) {     // Used by addWaiter
         this.nextWaiter = mode;
         this.thread = thread;
     }
     //用于条件等待队列
     Node(Thread thread, int waitStatus) { // Used by Condition
         this.waitStatus = waitStatus;
         this.thread = thread;
     }
 }

·head&tail&state

这是AQS中重要的3个属性也即双向链表的头节点和尾节点和一个用来标识锁数量(锁状态)的变量,可以说AQS就是围绕着这3个变量来进行操作的。

 private transient volatile Node head;
 private transient volatile Node tail;
 private volatile int state;
 private final boolean compareAndSetHead(Node update) {
     return unsafe.compareAndSwapObject(this, headOffset, null, update);
 }
 private final boolean compareAndSetTail(Node expect, Node update) {
     return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
 }
 protected final boolean compareAndSetState(int expect, int update) {
     // See below for intrinsics setup to support this
     return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
 }

·acquire()

互斥锁的抢锁方法,参数一般是1

 public final void acquire(int arg) {
     //由子类实现,当返回false(抢锁失败了才会进入下面的代码)
     if (!tryAcquire(arg) &&
         //先调用addWaiter()再调用acquireQueued()
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         //设置中断标志位
         selfInterrupt();
 }
 ​
 //可以看到AQS没有实现具体的抢锁逻辑,而是留给子类去实现
 protected boolean tryAcquire(int arg) {
     throw new UnsupportedOperationException();
 }

·addWaiter()

 private Node addWaiter(Node mode) 
     //创建一个节点并且保存当前线程的引用
     Node node = new Node(Thread.currentThread(), mode);
     // Try the fast path of enq; backup to full enq on failure
     //优化:在这里尝试快速的插入到队列的尾部,如果失败了会进入到正式的插入队列的方法(enq())
     Node pred = tail;//获取队尾节点
     if (pred != null) {//如果队尾不为null,则进行插入,否则进入enq
         node.prev = pred;//把当前节点的prev设置为pred,先将prev连上尾部
         if (compareAndSetTail(pred, node)) {//cas将node节点设置为tail(只能有一个线程成功,失败的则进入完整的enq()进行插入)
             pred.next = node;//再将next链上
             return node;//返回当前节点
         }
     }
     enq(node);
     return node;
 }

可以看出,对于链表的插入,是先链上prev,cas将自己设置为tail后再链上next。如下:

·enq()

 private Node enq(final Node node) {
     for (;;) {//死循环保证一定插入队列成功
         Node t = tail;//拿到尾节点
         if (t == null) { // 如果尾节点为null,代表未初始化,那么在这里需要初始化
             if (compareAndSetHead(new Node()))//CAS创建一个null的节点(伪节点)
                 tail = head;//tail和head都指向该伪节点
         } else { //否则,队列不为空,下面的操作和前面一样,先链上prev,然后casTail再链上next
             node.prev = t;
             if (compareAndSetTail(t, node)) {
                 t.next = node;
                 return t;
             }
         }
     }
 }

·acquireQueued()

当前节点已经在队列中了,那么下一步应该去阻塞了。

 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)) {//如果前面节点是头节点那么才会尝试tryAcquire()去抢锁,否则如果前驱节点不是头节点,那么直接去阻塞
                 setHead(node);//抢锁成功,将自己设置为头节点
                 p.next = null; // help GC 断开连接
                 failed = false;//标识成功
                 return interrupted;//返回中断位
             }
             //走到这里说明抢锁失败或者没机会枪锁,那么应该阻塞,但是在阻塞前应该要思考一个问题即我阻塞了,那么谁来将我唤醒?应该是前面一个节点来唤醒我吧,所以shouldParkAfterFailedAcquire()是将前驱节点的状态标识为后续有节点需要被唤醒的状态,然后才能去安心的去阻塞吧
             //当这里返回false的时候会再来一次循环,看看能否抢到锁!
             if (shouldParkAfterFailedAcquire(p, node) &&
                 //没办法了,必须阻塞去
                 /*
                      private final boolean parkAndCheckInterrupt() {
                         LockSupport.park(this);//阻塞去
                         return Thread.interrupted();//被唤醒的时候返回中断标志位,因为中断也能唤醒park()
                     }
                 */
                 parkAndCheckInterrupt())
                 interrupted = true;
         }
     } finally {
         if (failed)
             cancelAcquire(node);
     }
 }

·shouldParkAfterFailedAcquire()

pred是前驱节点,node是当前节点

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     int ws = pred.waitStatus;//获得前驱节点的状态
     if (ws == Node.SIGNAL)//如果就是-1,那么不用设置,直接返回true
         /*
              * This node has already set status asking a release
              * to signal it, so it can safely park.
              */
         return true;
     if (ws > 0) {//如果大于0,则需要往前找到一个不是>0的节点,然后链上
         /*
              * Predecessor was cancelled. Skip over predecessors and
              * indicate retry.
              */
         do {
             node.prev = pred = pred.prev;
         } while (pred.waitStatus > 0);
         pred.next = node;
     } else {//到这里意味着状态为0或者为-3,那么将状态cas为-1(0代表初始状态,-3代表传播状态)
         /*
              * waitStatus must be 0 or PROPAGATE.  Indicate that we
              * need a signal, but don't park yet.  Caller will need to
              * retry to make sure it cannot acquire before parking.
              */
         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
     }
     //可以看到,这里将状态位设置完成后,返回false,意味着先不会去阻塞,而是再次循环一次,也许在这次我就能拿到锁了,
     //在Doug lea的代码中只要涉及到阻塞,那么不用想,肯定在阻塞前会想法设法的做些什么”事情“,能不要阻塞就不阻塞。
     return false;
 }
 ​

对于上面代码中前驱节点的状态>0的操作(node节点需要一直往前找,直到状态不大于0为止)

·parkAndCheckInterrupt()

 private final boolean parkAndCheckInterrupt() {
     LockSupport.park(this);//阻塞
     return Thread.interrupted();//返回是否被中断的标识,true代表被中断
 }

·cancelAcquire()

发生异常,取消节点获取锁的方法。

 private void cancelAcquire(Node node) {
     if (node == null)//节点为null,直接返回
         return;
 ​
     node.thread = null;//将线程对象置为null
 ​
     Node pred = node.prev;//获取当前节点的前驱节点
     while (pred.waitStatus > 0)//同理找到最后一个状态不大于0的节点,意味着会帮忙清理其余被取消的节点     
         node.prev = pred = pred.prev;
     //保存最后一个节点状态不大于0的next节点
     Node predNext = pred.next;
 ​
     //将当前节点的状态设置为取消状态
     node.waitStatus = Node.CANCELLED;
 ​
     //如果被取消的节点是尾节点,那么CAS将尾节点设置为前面找到的那个状态>0的节点
     //如果cas成功,那么将其next指向null
     if (node == tail && compareAndSetTail(node, pred)) {
         compareAndSetNext(pred, predNext, null);
     } else {//否则:被取消的节点不是尾节点或者casTail失败(有竞争)
         int ws;
         if (pred != head &&//如果前驱节点不是头节点并且前驱节点的状态=11
             ((ws = pred.waitStatus) == Node.SIGNAL ||
                            //或者前驱节点的状态<=0并且CAS将前驱节点的状态设置为-1成功并且前驱节点的线程不为null
              (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
             pred.thread != null) {
             //满足其中一个就保存node的next,方便后面cas
             Node next = node.next;
             //如果next不为null并且next的状态<=0,则cas将pred的next链上next
             if (next != null && next.waitStatus <= 0)
                 compareAndSetNext(pred, predNext, next);
         } else {//前面的节点是头节点,那么就需要唤醒后一个节点
             unparkSuccessor(node);
         }
 ​
         node.next = node; // help GC
     }
 }

·acquireShared()

共享锁的抢锁实现

public final void acquireShared(int arg) {
    //由子类实现抢锁的逻辑,返回值<0代表抢锁失败,失败的线程进入doAcquireShared方法
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

·doAcquireShared()

private void doAcquireShared(int arg) {
    //入队列,从这里就可以看出和互斥锁的区别了,这里节点是共享状态
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;//标识是否失败
    try {
        boolean interrupted = false;//标识是否被中断
        for (;;) {
            final Node p = node.predecessor();//获得前驱节点
            /*
            	判断前驱节点是否是头节点,如果是则尝试获取锁,
            	如果返回值>=0,则将自己设置为头节点
            	并且唤醒同样在等待且是SHARED状态的节点
            */
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //否则如果抢锁失败或者无法抢锁,则应该将前面节点设置为-1,然后阻塞去
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)//如果失败需要取消节点
            cancelAcquire(node);
    }
}

·setHeadAndPropagate()

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; //保存最新头节点的快照
    /*
      private void setHead(Node node) {
        head = node;//将头节点设置为自己
        node.thread = null;//将线程置为null
        node.prev = null;//将prev引用置为null
    }
    */
    setHead(node);//将当前节点设置为头节点
    /*
    	propagate是前面tryAcquireShared()返回的值
    	下面if()中有5个判断,只要满足其中一个就允许调用doReleaseShared()方法
    	1.propagate > 0 代表锁还有余量
    	2.h(头节点的快照,此时头节点可能被改变了,但是这里使用的是上面保存的快照)为null
    	3.h的状态<0
    	4.获取最新的头节点h还是为null
    	5.最新头节点h的状态小于0
    */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        //如果满足条件,则拿到当前节点的后继节点
        Node s = node.next;
        //如果后继节点为null或者后继节点是共享状态,那么执行doReleaseShared()
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

这里可能对于if()判断有点难以理解,确实,这里可以说是AQS中最为复杂的逻辑,不过后面我会再详细讲解。现在只需要知道简单的逻辑即可。

·doReleaseShared()

注意这里可能会出现一次伪唤醒问题!

private void doReleaseShared() {
    for (;;) {//死循环
        Node h = head;//拿到最新的头节点
        //如果头节点不为null并且头,尾节点不相等,那么进入该方法(如果头尾节点相等,那么代表队列为空,就不需要做什么了)
        if (h != null && h != tail) {
            int ws = h.waitStatus;//获取头节点(快照)的状态
            if (ws == Node.SIGNAL) {//如果头节点的状态是-1(代表后面有要被唤醒的节点)
                //CAS将当前头节点的状态设置为0,cas成功,取反就是false即执行unpark()操作
                //cas失败,取反就是true,即退出循环
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            //否则,即头节点的状态不为-1,那么如果头节点的状态为0,那么将其cas为-3(即传播状态)
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        //如果头节点没有被改变,那么退出循环
        if (h == head)                   // loop if head changed
            break;
    }
}

现在只需要理解字面意思即可,等到了具体实现类的再具体分析!

·unparkSuccessor()

唤醒后续节点的操作

private void unparkSuccessor(Node node) {
    //拿到节点的状态
    int ws = node.waitStatus;
    if (ws < 0) //如果状态<0那么cas将状态设置为0,前面明明cas将节点的状态设置为0了,为什么这里还要判断是否<0呢?
        compareAndSetWaitStatus(node, ws, 0);

    /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
    //拿到当前节点的后继节点
    Node s = node.next;
    /*
    	如果后继节点为null
    	或者后继节点的状态>0(被取消了)
    	那么将s置为null(这里置为null是if()中也许前面是false而后面是true,
    	则需要将该被取消的节点置为null)
    	然后从尾节点开始,一直向前遍历,直到找到第一个状态<=0的节点,然后进行唤醒
    */
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

·releaseShared()

共享锁释放锁的操作。

public final boolean releaseShared(int arg) {
    //调用子类释放锁的代码,返回true代表释放成功,执行上面的逻辑
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

·release()

互斥锁释放锁的操作。

public final boolean release(int arg) {
    //调用子类释放锁的逻辑,返回true代表释放成功。
    if (tryRelease(arg)) {
        Node h = head;//拿到头节点
        //头节点不为null,并且头节点的状态不为0,那么唤醒后续线程
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

2.条件变量Condition

·ConditionObject

public class ConditionObject implements Condition, java.io.Serializable {
    private transient Node firstWaiter;
    private transient Node lastWaiter;
    /** Mode meaning to reinterrupt on exit from wait */
    private static final int REINTERRUPT =  1; //重复中断标志位
    /** Mode meaning to throw InterruptedException on exit from wait */
    private static final int THROW_IE    = -1; //发生异常状态位
}

·await()

需要等待条件则调用await()去条件队列中等待。

public final void await() throws InterruptedException {
    if (Thread.interrupted()) //如果被中断了直接抛出中断异常
        throw new InterruptedException();
    Node node = addConditionWaiter();//将线程节点加入到阻塞队列中去
    //释放在AQS竞争队列中当前节点后面的等待锁的节点(不释放锁别人怎么拿锁然后唤醒你呢?)
    int savedState = fullyRelease(node);
    //中断模型
    int interruptMode = 0;
    /*
    	判断当前节点是否在同步队列中
    	false代表当前节点在等待队列(这是我们想要的)
    	true代表当前在同步竞争队列
    */
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);//在等待队列中阻塞
        //执行到这里说明被唤醒了:要么是正常唤醒要么是被中断唤醒的
        //当发生了中断会结束循环,如果是正常被唤醒
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //尝试调用acquireQueued()来获取锁,如果获取锁成功并且没被中断
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        //设置标识位
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        //清理被取消的节点
        unlinkCancelledWaiters();
    //如果被中断,那么抛出中断异常
    if (interruptMode != 0)
        /*
           private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }
        */
        reportInterruptAfterWait(interruptMode);
}

·addConditionWaiter()

private Node addConditionWaiter() {
    Node t = lastWaiter;//拿到等待队列中的最后一个节点
    // If lastWaiter is cancelled, clean out.
    //如果t不为null并且t的状态不为等待状态(说明被取消了),那么做清理工作
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();//清除
        t = lastWaiter;//然后将清理过后的最后一个节点赋值给t
    }
    //创建一个节点,节点的状态为等待状态(CONDITION)
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)//如果t为空,代表等待队列中为空
        firstWaiter = node;
    else//如果等待队列不为空,那么将该节点链在尾节点之后
        t.nextWaiter = node;
    lastWaiter = node;
    return node;//返回当前节点
}

·unlinkCancelledWaiters()

清理在条件等待队列中被取消的节点

private void unlinkCancelledWaiters() {
    Node t = firstWaiter;//拿到第一个节点
    Node trail = null;//临时节点
    while (t != null) {//从头节点开始
        Node next = t.nextWaiter;//头节点的下一个节点
        //如果头节点的状态不是等待状态(被取消了)
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;//那么将t.nextWaiter断开
            if (trail == null)//如果trail=null,那么代表是清理头节点,则将头节点设置为头节点的next节点
                firstWaiter = next;
            else //否则,不是清理头节点(trail代表头节点),那么将被取消的节点从链表中移除,即trail.next=t.next(单链表的删除操作)
                trail.nextWaiter = next;
            if (next == null)//如果没有后继节点,那么将trail赋值给last节点
                lastWaiter = trail;
        }
        else //否则,即头节点没被取消,则将头节点设置给trail
            trail = t;
        t = next;//将t往后移动
    }
}

下面只举例了头节点没被取消的例子,关于被取消的例子分析过程是一样,只要把变量标记好了就不会出错。

·fullyRelease()

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        //保存当前线程的state状态
        int savedState = getState();
        //调用AQS中的release()释放在同步队列中的等待节点
        if (release(savedState)) {
            failed = false;
            return savedState;//返回
        } else {
            //如果release失败,那么直接抛出异常,这是直接调用await()的结果
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)//如果失败则将节点状态设置为取消状态
            node.waitStatus = Node.CANCELLED;
    }
}

·checkInterruptWhileWaiting()

 private int checkInterruptWhileWaiting(Node node) {
     return Thread.interrupted() ?//如果为false(没被中断),那么返回0,
         (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) ://否则如果被中断,那么执行此处代码
     0;
 }

·transferAfterCancelledWait()

 final boolean transferAfterCancelledWait(Node node) {
     //CAS将节点的状态由等待转为0,然后将节点入竞争队列
     //从这里可以看出,等待节点被唤醒后会被转移到同步队列中
     if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
         enq(node);
         return true;
     }
     //上面cas失败的,在这里判断如果没在同步队列中,那么释放时间片等待别的线程将该节点入同步队列中,然后返回false
     while (!isOnSyncQueue(node))
         Thread.yield();
     return false;
 }
 可以看出这个方法要么是返回true,要么是返回false:
     如果当前线程CAS成功,那么返回true
     如果当前线程CAS失败,那么返回false,
 如果返回true,那么checkInterruptWhileWaiting将会返回THROW_IE
 如果返回false,那么checkInterruptWhileWaiting将会返回REINTERRUPT

·signal()

 public final void signal() {
     //必须保证是在AQS锁下面执行的操作,不然直接报错
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     //拿到第一个节点
     Node first = firstWaiter;
     if (first != null)
         //如果节点不为空,那么调用下面方法
         doSignal(first);
 }
 ​
 //-doSignal()
 private void doSignal(Node first) {
     do {
         //如果first.nextWaiter为null,说明等待队列中只有一个节点,那么做数据清除即可
         if ( (firstWaiter = first.nextWaiter) == null)
             lastWaiter = null;
         first.nextWaiter = null;
     } while (!transferForSignal(first) && //如果失败继续唤醒下一个节点,如果成功这里就是false,那么停止唤醒
              (first = firstWaiter) != null);
 }
 ​
 //transferForSignal()
 final boolean transferForSignal(Node node) {
     //cas将状态由等待状态设置为0,如果成功就继续执行下面的入队操作,如果失败就返回false
     if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
         return false;
 ​
     //当前signal()的线程CAS成功,那么将该节点入同步竞争队列去
     Node p = enq(node);//获得前驱节点
     int ws = p.waitStatus;//获得前驱节点的状态
     //如果前驱节点的状态>0 
     //或者cas将前驱节点的状态设置为-1失败,那么唤醒当前线程
     if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
         LockSupport.unpark(node.thread);
     return true;
 }

这里的逻辑比较难懂,画图可能比较好理解。

无标题网络拓扑图(8) 枫叶云笔记

·signalAll()

public final void signalAll() {
    if (!isHeldExclusively()) //必须保证是在持有锁的状态下才能调用该方法
        throw new IllegalMonitorStateException();
    Node first = firstWaiter; //拿到第一个节点
    if (first != null)//如果不为null,即链表不为null
        doSignalAll(first);
}

//-doSignalAll()
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;将头尾节点置的引用置为null
    do {
        Node next = first.nextWaiter;//保存下一个节点
        first.nextWaiter = null;//断开链接
        transferForSignal(first);//入竞争队列
        first = next;//从next开始
    } while (first != null);
}

signalAll()是将条件等待队列中的所有节点都移动到同步竞争队列去。

总结

AQS就是由一个state变量和一个双向链表组成,只提供了出队入队和阻塞的逻辑,对于抢锁的逻辑由子类实现。

而里面的条件变量则是单向链表,在被唤醒后会被转移到AQS队列中去竞争(意味着即使被唤醒也不一定能抢到锁)

3.ReentrantLock

互斥锁的实现,有公平和非公平的实现。先来看看两者之间的区别。具体实现逻辑都差不多,但是一般来说是使用非公平的,所以源码分析就以非公平的为例子。

·公平和非公平的区别

//非公平
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

//公平
final void lock() {
    acquire(1);//!hasQueuedPredecessors()
}

可以看到非公平和公平的区别就在于公平是先去看看队列中是否有人排队,而非公平锁上来就是直接cas抢锁,
所以公平和非公平是在于是否立即抢锁,而只要入队之后就没有公平和非公平之说了。

·示例

开启了3个线程

@Slf4j
public class T4 {
    public static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    reentrantLock.lock();
                    log.debug("{}拿到了锁····", Thread.currentThread().getName());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    log.debug("{}释放了锁····", Thread.currentThread().getName());
                    reentrantLock.unlock();

                }

            }, "thread-" + i).start();

        }


    }
}

//结果
2024-01-27 20:24:47 [thread-0] - thread-0拿到了锁····
2024-01-27 20:24:50 [thread-0] - thread-0释放了锁····
2024-01-27 20:24:50 [thread-1] - thread-1拿到了锁····
2024-01-27 20:24:53 [thread-1] - thread-1释放了锁····
2024-01-27 20:24:53 [thread-2] - thread-2拿到了锁····
2024-01-27 20:24:56 [thread-2] - thread-2释放了锁····
    
可以看到是互斥使用的

·lock()

以非公平为例子。

public void lock() {
    sync.lock();
}

final void lock() {
    //非公平互斥锁直接CAS抢锁,如果成功设置当前线程(自己)为独占状态
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else//否则抢锁失败,进入主体方法
        acquire(1);
}

public final void acquire(int arg) {
    //调用tryAcquire()进行抢锁,arg=1
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();//拿到当前线程
    int c = getState();//获取状态(0代表没有锁,1代表锁被抢了)
    if (c == 0) {//此时没有锁,则CAS抢锁
        if (compareAndSetState(0, acquires)) {
            //抢锁成功,将当前线程设置为独占线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
   //否则,也即此时有别的线程占用锁,判断是否是当前线程(可重入锁的实现)
    else if (current == getExclusiveOwnerThread()) {//如果是当前线程再次拿锁
        int nextc = c + acquires;//那么将拿锁次数+1
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;//抢锁失败,返回false
}

这里的逻辑就是子类实现抢锁的逻辑,子类只需要告诉父类抢锁的结果然后出队入队的操作就交给父类(AQS)来做,一旦抢锁失败返回false,那么就会进行入队操作。

·addWaiter()

将自己入队列,先链prev,casTail后再链上next

private Node addWaiter(Node mode) 
    //创建一个节点并且保存当前线程的引用
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
	//优化:在这里尝试快速的插入到队列的尾部,如果失败了会进入到正式的插入队列的方法(enq())
    Node pred = tail;//获取队尾节点
    if (pred != null) {//如果队尾不为null,则进行插入,否则进入enq
        node.prev = pred;//把当前节点的prev设置为pred,先将prev连上尾部
        if (compareAndSetTail(pred, node)) {//cas将node节点设置为tail(只能有一个线程成功,失败的则进入完整的enq()进行插入)
            pred.next = node;//再将next链上
            return node;//返回当前节点
        }
    }
    enq(node);//快速插入失败
    return node;
}

在enq()中也有上面这段插入的方法,那么为什么这里要重复写一次呢?这里是一个小的性能体现,当并发很大时,多个线程需要同时插入到队列中,那么当线程执行到这里的时候提供了一个快速插入的机会,如果插入成功就直接返回,而不需要和别的线程竞争了,因为在这里总会有一个线程cas成功,也即减少了后续的竞争。

·acquireQueued()

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)) {//如果前面节点是头节点那么才会尝试tryAcquire()去抢锁,否则如果前驱节点不是头节点,那么直接去阻塞
                setHead(node);//抢锁成功,将自己设置为头节点
                p.next = null; // help GC 断开连接
                failed = false;//标识成功
                return interrupted;//返回中断位
            }
            //走到这里说明抢锁失败或者没机会枪锁,那么应该阻塞,但是在阻塞前应该要思考一个问题即我阻塞了,那么谁来将我唤醒?应该是前面一个节点来唤醒我吧,所以shouldParkAfterFailedAcquire()是将前驱节点的状态标识为后续有节点需要被唤醒的状态,然后才能去安心的去阻塞吧
            //当这里返回false的时候会再来一次循环,看看能否抢到锁!
            if (shouldParkAfterFailedAcquire(p, node) &&
                //没办法了,必须阻塞去
                /*
                	 private final boolean parkAndCheckInterrupt() {
        				LockSupport.park(this);//阻塞去
        				return Thread.interrupted();//被唤醒的时候返回中断标志位,因为中断也能唤醒park()
    				}
                */
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)//如果有异常则取消抢锁的行为
            cancelAcquire(node);
    }
}

这里需要注意的一点是当要阻塞的线程调用shouldParkAfterFailedAcquire()修改了前面一个线程的状态后会再次返回一次尝试拿锁。

·unlock()

释放锁的逻辑

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {//尝试释放锁(arg=1)
        //
        Node h = head;//拿到头节点
        //头节点不为空并且头节点的状态不为0,那么唤醒后续的线程
        if (h != null && h.waitStatus != 0)
            //唤醒后续的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

//重入了几次就需要释放几次锁
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//获取state状态并且-1
    //如果不是当前持有线程则抛出异常(没持有锁怎么释放锁?Redis分布式锁貌似存在这个问题但是是需要我们自己解决的)
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//如果c=0,代表只持有1把锁,那么释放成功,但独占线程标识置为null
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);//设置state=0(代表无锁状态)
    return free;
}

·unparkSuccessor()

private void unparkSuccessor(Node node) {
    //拿到节点的状态
    int ws = node.waitStatus;
    if (ws < 0) //如果状态<0那么cas将状态设置为0,前面明明cas将节点的状态设置为0了,为什么这里还要判断是否<0呢?
        compareAndSetWaitStatus(node, ws, 0);

    /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
    //拿到当前节点的后继节点
    Node s = node.next;
    /*
    	如果后继节点为null
    	或者后继节点的状态>0(被取消了)
    	那么将s置为null(这里置为null是if()中也许前面是false而后面是true,
    	则需要将该被取消的节点置为null)
    	然后从尾节点开始,一直向前遍历,直到找到第一个状态<=0的节点,然后进行唤醒
    */
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

总结一下:ReentrantLock是一把互斥锁,其中的state变量0代表无锁,1代表锁被抢了,并且支持可重入锁。

·可重入锁示例(拿了几次就要释放几次)

public static void main(String[] args) {
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            try {
                reentrantLock.lock();
                reentrantLock.lock();
                log.debug("{}拿到了锁····", Thread.currentThread().getName());
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                log.debug("{}释放了锁····", Thread.currentThread().getName());
                reentrantLock.unlock();

            }

        }, "thread-" + i).start();

    }
}

//结果
2024-01-28 10:34:24 [thread-0] - thread-0拿到了锁····
2024-01-28 10:34:27 [thread-0] - thread-0释放了锁····
可以看到因为t0拿了2次锁但是只释放了1次,所以后续线程拿不到锁了

4.Condition示例

·示例1

@Slf4j
public class ReentrantLockTest {

    public static ReentrantLock reentrantLock = new ReentrantLock();
    public static Condition condition = reentrantLock.newCondition();

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            try {
                reentrantLock.lock();
                log.debug("{}线程拿到了锁,执行逻辑····", Thread.currentThread().getName());
                condition.await();
                //todo
                log.debug("{}doSomeThing", Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
                log.debug("{}线程释放了锁·····", Thread.currentThread().getName());
            }
        }, "thead-1").start();
        sleep(3);
        new Thread(() -> {
            try {
                reentrantLock.lock();
                log.debug("{}线程拿到了锁,执行逻辑····", Thread.currentThread().getName());
                //todo
                log.debug("{}doSomeThing", Thread.currentThread().getName());
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
                log.debug("{}线程释放了锁·····", Thread.currentThread().getName());
            }
        }, "thead-2").start();
    }

    public static void sleep(int time) throws InterruptedException {
        Thread.sleep(time * 1000);
    }
}

//结果
2024-01-28 10:43:51 [thead-1] - thead-1线程拿到了锁,执行逻辑····
2024-01-28 10:43:54 [thead-2] - thead-2线程拿到了锁,执行逻辑····
2024-01-28 10:43:54 [thead-2] - thead-2doSomeThing
2024-01-28 10:43:54 [thead-2] - thead-2线程释放了锁·····
2024-01-28 10:43:54 [thead-1] - thead-1doSomeThing
2024-01-28 10:43:54 [thead-1] - thead-1线程释放了锁·····
    
可以看到t1释放了锁但是好像没有释放锁但是t2却拿到了锁,其实在调用await()的时候就已经释放了锁,不然t2是不可能拿到锁的

·示例2

package com.wjcoder.juc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class T4 {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            try {
                lock.lock();
                condition.await();
                System.out.println("1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
        Thread.sleep(200);
        new Thread(() -> {
            try {
                lock.lock();
                condition.await();
                System.out.println("2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
        Thread.sleep(200);
        new Thread(() -> {
            try {
                lock.lock();
                condition.signal();
                System.out.println("3");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
    }
}

//结果
3
1

线程1启动后调用await()去条件等待队列中阻塞了,然后sleep(200)后t2启动并且也是调用await()去条件队列中等待,并且是链在线程1后面的,sleep(200)后,t3启动调用了signal()然后打印3,并且会唤醒等待队友的头节点(t1),t1被唤醒后打印1,而t2无人唤醒。

5.ReentrantReadWriteLock

读写锁,即一把锁分为了2把锁,读锁和写锁

·示例

@Slf4j
public class T4 {

    static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();
    static int num = 1;

    public static void main(String[] args) throws Exception {

        new Thread(() -> {
            writeLock.lock();//上写锁
            num += 1;
            log.debug("{}-write some····-{}", Thread.currentThread().getName(), num);
            readLock.lock();//上读锁,和写锁互斥(上面不是有写锁吗?为什么这里还可以拿到读锁?锁降级)
            log.debug("{}-read some····-{}", Thread.currentThread().getName(), num);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            writeLock.unlock();
            readLock.unlock();
        }, "t1").start();
        Thread.sleep(200);
        new Thread(() -> {
            readLock.lock();//上读锁,和写锁互斥
            log.debug("{}-read some····-{}", Thread.currentThread().getName(), num);
            readLock.unlock();
        }, "t2").start();

    }
}

//结果
2024-01-28 11:39:10 [t1] - t1-write some····-2
2024-01-28 11:39:10 [t1] - t1-read some····-2
    注意因为t1没有释放写锁,所以t2只有在t1释放了写锁后才能获取读锁,也即读写互斥
2024-01-28 11:39:15 [t2] - t2-read some····-2

需要注意的是读锁和写锁是互斥的(但是对于同一线程来说可以先拿写锁再拿读锁,也即锁降级),但是不能先拿读锁再拿写锁(不支持锁升级)

·属性

//同样有公平实现和非公平实现,后续分析均以非公平为例
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);//初始化读锁
    writerLock = new WriteLock(this);//初始化写锁
}
private volatile int state; //AQS的state变量是int类型(32位)
abstract static class Sync extends AbstractQueuedSynchronizer {
    //AQS的state变量被分为了高16位和低16位,其中低16位代表互斥锁,高16位代表共享锁(读锁的数量)
    static final int SHARED_SHIFT   = 16;
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);//0000 0000 0000 0001 后续有16个0
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;//共享锁最大支持2^16-1个
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//互斥锁的最大重入次数2^16-1次
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }//获得读锁数量:右移16位
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }//获得写锁数量:与上0xffff ffff即可
    static final class HoldCounter {//每个线程共享锁的持有数量,由ThreadLocl维护
        int count = 0;
        // Use id, not reference, to avoid garbage retention
        final long tid = getThreadId(Thread.currentThread());
    }
    static final class ThreadLocalHoldCounter//用于生成和保存每个线程的读锁计数器
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {//调用get()的时候会调用该方法
            return new HoldCounter();
        }
    }
    //ThreadLocalHoldCounter类的实例
    private transient ThreadLocalHoldCounter readHolds;
    //缓存最后一个成功获取读锁的线程的计数器
    private transient HoldCounter cachedHoldCounter;
    //缓存第一个获取读锁的线程和它的计数器
    private transient Thread firstReader = null;
    private transient int firstReaderHoldCount;
    Sync() {
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }

·readLock.lock()

读锁的上锁逻辑

public void lock() {
    sync.acquireShared(1);
}

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

·tryAcquireShared()

共享锁的获取锁逻辑

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();//拿到当前线程
    int c = getState();//获得state变量
    if (exclusiveCount(c) != 0 &&//判断是否存在写锁,如果存在写锁那么继续判断持有写锁的线程是不是本身线程,如果不是则返回-1,即获取锁失败(互斥锁的实现),否则代表着当前无人持有写锁或者是锁降级的实现
        getExclusiveOwnerThread() != current)
        return -1;
    //拿到写锁的数量(快照)
    int r = sharedCount(c);
    //判断读线程是否应该被阻塞
    if (!readerShouldBlock() && //如果不需要阻塞
        r < MAX_COUNT &&	    //并且读锁没有超过最大值
        compareAndSetState(c, c + SHARED_UNIT)) {//则CAS将读锁数量+1.这里是c+65536,相当于对高16位+1
        if (r == 0) {//如果之前的读锁数量是0,那么代表当前线程是第一个获得读锁的线程
            firstReader = current;//那么将当前线程缓存到firstReader
            firstReaderHoldCount = 1;//第一个读线程持有读锁的数量为1
        } else if (firstReader == current) {//否则如果当前线程是第一个获取读锁的线程,并且不是第一次,那么直接对该线程缓存的firstReaderHoldCount+1即可
            firstReaderHoldCount++;
        } else {//否则不是第一个获取读锁的线程,
            HoldCounter rh = cachedHoldCounter;//那么获得该线程的缓存计数器,第一次到这里的时候是为空的
            if (rh == null || rh.tid != getThreadId(current))//如果rh为null或者rh中的id不是当前线程的id
                cachedHoldCounter = rh = readHolds.get();//那么调用get()来创建rh(对应initialValue方法)
            else if (rh.count == 0)//如果是第一次创建的话,那么保存到threadLocal中
                readHolds.set(rh);
            rh.count++;//对当前线程持有读锁的数量+1
        }
        return 1; //返回1,代表获取读锁成功
    }
    //如果当前读线程应该被阻塞(头节点的next节点是写线程在阻塞,那么当前读线程应该被阻塞)或者超过最大读锁数量或者CAS修改state失败,那么进入到完整的获取读锁的方法中
    return fullTryAcquireShared(current);
}

·读线程应该被阻塞的例子

package com.wjcoder.juc;

import lombok.extern.slf4j.Slf4j;

import javax.rmi.CORBA.Util;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Slf4j
public class T3 {
    static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            readLock.lock();
            log.debug("{}-todo", Utils.getThreadName());
            try {
                Utils.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            readLock.unlock();
        }, "t1");


        Thread t2 = new Thread(() -> {
            writeLock.lock();
            log.debug("{}-todo", Utils.getThreadName());
            try {
                Utils.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            writeLock.unlock();
        }, "t2");

        Thread t3 = new Thread(() -> {
            readLock.lock();
            log.debug("{}-todo", Utils.getThreadName());
            try {
                Utils.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            readLock.unlock();
        }, "t3");
        t1.start();
        Utils.sleep(1);
        t2.start();
        Utils.sleep(1);
        t3.start();

    }
}

//结果
2024-01-28 13:52:54 [t1] - t1-todo
2024-01-28 13:52:56 [t2] - t2-todo
2024-01-28 13:52:58 [t3] - t3-todo
    可以看到在t1持有读锁的时候,t2来拿写锁,因为读锁被t1拿了,所以此时拿不到写锁,则去阻塞,然后t3来拿读锁,虽然此时运行的线程持有读锁,但是因为前面有写线程在等待,所以该读线程也要去阻塞,等到写线程完成后才能执行。所以结果才是t1,t2,t3,而不是t1,t3,t2.

·readerShouldBlock()

final boolean readerShouldBlock() {
    return apparentlyFirstQueuedIsExclusive();
}

//-apparentlyFirstQueuedIsExclusive()
//如果同步队列中的第一个节点的状态不是SHARED那么代表当前读线程需要阻塞,即返回true,否则返回false
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    return (h = head) != null && //头节点不为null
        (s = h.next)  != null && //头节点的后继节点不为空
        !s.isShared()         && //后继节点不是共享状态的节点
        s.thread != null;		 //后继节点的线程不为空
    							 //则返回true
}

·fullTryAcquireShared()

final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) { //死循环
        int c = getState();//拿到state的状态
        if (exclusiveCount(c) != 0) {//如果此时有写锁
            if (getExclusiveOwnerThread() != current)//并且持有写锁的线程不是当前线程,那么返回-1
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {//判断是否需要阻塞,如果需要(意味着同步队列中的第二个节点是写线程在等待)
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {//如果当前线程是第一个获取读锁的线程,那么不阻塞
                // assert firstReaderHoldCount > 0;
            } else {//如果不是第一个获取读锁的线程,上面的rh=null,所以这里的判断是一定进去的
                if (rh == null) {
                    rh = cachedHoldCounter;//拿到最后一个获取读锁线程中的读锁计数器
                    if (rh == null || rh.tid != getThreadId(current)) {//如果为null不是当前线程的计数器
                        rh = readHolds.get();//那么从threadLocl中获取对应的计数器
                        if (rh.count == 0)//如果获取读锁的数量为0,那么代表该线程是第一次获取读锁(但是前面有写线程在等待,那么在下面就会返回-1,也即获取读锁失败应该去阻塞,也即如果在写线程进入同步队列之前如果当前线程已经拿到过读锁,那么此时不应该被阻塞,也即继续往下执行)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        //下面的代码也许是正常执行的读锁获取锁操作,也许是上面逃过阻塞的读线程执行的操作
        
        //如果读锁的数量超过最大值,直接抛出异常
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //CAS增加读锁的数量(+1)
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            //如果之前读锁的数量(这里的c是在上面缓存的c,是旧值,虽然此时c已经被修改了)为0,
            if (sharedCount(c) == 0) {//那么代表当前线程是第一个获取读锁的线程,
                firstReader = current;//保存第一个获取读锁的线程
                firstReaderHoldCount = 1;//保存其获得读锁的数量
            } else if (firstReader == current) {//如果是第一个获取读锁的线程,那么+1
                firstReaderHoldCount++;
            } else {//不是第一个获取读锁的线程,那么获取其对应的缓存将其读锁数量+1即可,注意执行到这里可能是正常的读线程来获取锁也可能是上面逃过阻塞的线程执行到这里,不管怎么样,都是对其读锁数量的缓存+1(rh.count++;)
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release 缓存最后一次获取读锁线程的rh
            }
            return 1;//返回获取读锁成功
        }
    }
}

·能逃过阻塞的读线程示例

package com.wjcoder.juc;

import lombok.extern.slf4j.Slf4j;

import javax.rmi.CORBA.Util;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Slf4j
public class T3 {
    static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            readLock.lock();
            log.debug("{}-获取读锁", Utils.getThreadName());
            Utils.sleep(2000);
            readLock.unlock();
        }, "t1");


        Thread t2 = new Thread(() -> {
            writeLock.lock();
            log.debug("{}-获取写锁", Utils.getThreadName());
            Utils.sleep(2000);
            writeLock.unlock();
        }, "t2");

        Thread t3 = new Thread(() -> {
            readLock.lock();
            log.debug("{}-获取读锁", Utils.getThreadName());
            Utils.sleep(2000);
            readLock.lock();
            log.debug("{}-再次获取锁", Utils.getThreadName());
            Utils.sleep(2000);
            readLock.unlock();
            readLock.unlock();
        }, "t3");
        t1.start();
        Utils.sleep(1000);
        t3.start();
        Utils.sleep(1000);
        t2.start();


    }
}


//结果
2024-01-28 14:20:55 [t1] - t1-获取读锁
2024-01-28 14:20:56 [t3] - t3-获取读锁
2024-01-28 14:20:58 [t3] - t3-再次获取锁
2024-01-28 14:21:00 [t2] - t2-获取写锁
    可以看到和上面一个例子的区别了。t1获取读锁,然后t3获取读锁,这个时候t2获取写锁(但是因为有线程获取写锁,所以失败去阻塞了),然后t3再次获取锁,因为在t2阻塞前获取过一次读锁,所以这里还能再次获取到。

·doAcquireShared()

获取读锁失败后的操作。

//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) {//如果抢锁成功(注意对于读写锁来说抢锁成功那么这里永远返回1,那么0是给谁的呢?)
                    setHeadAndPropagate(node, r);//将自己设置为头节点并且唤醒后续读线程
                    p.next = null; // help GC
                    if (interrupted)//如果被中断了,那么设置中断标志位
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //将前面的一个节点的状态设置为-1,如果已经是-1了,那么直接去阻塞,否则再次循环尝试拿一次锁
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())//阻塞
                interrupted = true;
        }
    } finally {
        if (failed)//获取锁失败,则取消抢锁的线程
            cancelAcquire(node);
    }
}

· setHeadAndPropagate()

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // 保存头节点的快照
    setHead(node);//将自己设置为头节点
    //propagate > 0 只要执行到这里(对于读写锁来说)这里第一个一定是为true的,所以后面的就不用看了(那么是留给谁用的?)
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;//拿到当前节点的后续节点,如果是共享的则唤醒它
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

·doReleaseShared()

private void doReleaseShared() {
    for (;;) {
        Node h = head;//保存头节点的快照
        if (h != null && h != tail) {//如果头节点不为空并且链表不为空
            int ws = h.waitStatus;//获取头节点的状态
            if (ws == Node.SIGNAL) {//如果是-1,那么将其CAS为0,然后唤醒后续线程
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            //否则头节点的状态为0,那么cas将其设置为-3(传播状态),cas失败的线程会再次循环
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        //如果头节点没有改变,那么退出循环
        if (h == head)                   // loop if head changed
            break;
    }
}

这里有点难以理解,注意unlock()方法的最后也是会调用该方法,所以这里在修改状态的时候才会要cas。那么来分析一下这里为什么要这么做。

无标题网络拓扑图(9) 枫叶云笔记

·unlock()

 public void unlock() {
     sync.releaseShared(1);
 }
 ​
 public final boolean releaseShared(int arg) {
     if (tryReleaseShared(arg)) {//释放锁
         doReleaseShared();
         return true;
     }
     return false;
 }
 ​
 protected final boolean tryReleaseShared(int unused) {
     Thread current = Thread.currentThread();//拿到当前线程
     if (firstReader == current) {//如果是第一个获取读锁的线程
         // assert firstReaderHoldCount > 0;
         if (firstReaderHoldCount == 1)//如果没有锁重入,那么将firstReader置为null
             firstReader = null;
         else//否则将其获取锁的数量-1
             firstReaderHoldCount--;
     } else {//否则不是第一个获取读锁的线程
         HoldCounter rh = cachedHoldCounter;//拿到其缓存读锁数量的计数器,如果只有1,那么移除,否则减1
         if (rh == null || rh.tid != getThreadId(current))
             rh = readHolds.get();
         int count = rh.count;
         if (count <= 1) {
             readHolds.remove();
             if (count <= 0)
                 throw unmatchedUnlockException();
         }
         --rh.count;
     }
     for (;;) {//死循环,将读锁的数量-1,很明显必须减到0才算真正意义上的释放读锁
         int c = getState();
         int nextc = c - SHARED_UNIT;
         if (compareAndSetState(c, nextc))
             // Releasing the read lock has no effect on readers,
             // but it may allow waiting writers to proceed if
             // both read and write locks are now free.
             return nextc == 0;
     }
 }

·writerLock.lock()

看到这样的代码就不用多说了,读写锁中的写锁就是一把互斥锁

public void lock() {
    sync.acquire(1);
}
public final void acquire(int arg) {
    //拿写锁
    if (!tryAcquire(arg) &&
        //获取锁失败,入队操作就不赘述了
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

·tryAcquire()

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();//拿到当前线程
    int c = getState();//获取状态
    int w = exclusiveCount(c);//获取写锁的数量
    if (c != 0) {//如果c!=0,那么代表有锁
        //有锁但是没有写锁,那就说明有读锁,直接返回false
        //或者有写锁,但是不是当前线程,直接返回false(写写互斥)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        //否则可以拿到锁,如果超出最大数量抛出异常
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        //设置写锁的数量
        setState(c + acquires);
        return true;
    }
    //如果c=0,也即没有锁,返回false是为了执行后面的逻辑
    //因为可能同一时刻可能有多个写锁来竞争
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))//cas设置写锁的数量(抢锁),成功的则将自己设置为独占,失败的返回false
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

6.Semaphore

信号量

·示例

 @Slf4j
 public class SemaphoreTest {
     public static void main(String[] args) {
         Semaphore semaphore = new Semaphore(3);
         for (int i = 0; i < 4; i++) {
             new Thread(() -> {
                 try {
                     log.debug("{}--begin", Utils.getThreadName());
                     semaphore.acquire();
                     log.debug("{}--doSomeThing", Utils.getThreadName());
                     Utils.sleep(5000);
                     semaphore.release();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }, "thread-" + i).start();
         }
     }
 }
 ​

·结果

2024-01-29 10:49:25 [thread-0] - thread-0--begin
2024-01-29 10:49:25 [thread-3] - thread-3--begin
2024-01-29 10:49:25 [thread-1] - thread-1--begin
2024-01-29 10:49:25 [thread-2] - thread-2--begin
2024-01-29 10:49:25 [thread-0] - thread-0--doSomeThing
2024-01-29 10:49:25 [thread-3] - thread-3--doSomeThing
2024-01-29 **10:49:25** [thread-1] - thread-1--doSomeThing
2024-01-29 **10:49:30** [thread-2] - thread-2--doSomeThing

看执行的时间可以发现线程0,1,3,是正常执行的,但是线程2确等待了5s才执行,这是因为信号量只有3个,而0,1,3,已经用了3个了,所以线程2在申请使用也即调用acquire()的时候会去阻塞,当有线程释放也即调用release()的时候才会醒来去抢信号量(因为这里只有线程3一个,所以这里没有竞争)。接下来就看看其是如何实现的。

·构造方法和属性

 private final Sync sync;
 public Semaphore(int permits) {
     sync = new NonfairSync(permits);//默认采用非公平的
 }
 ​
 NonfairSync(int permits) {
     super(permits);
 }
 ​
 Sync(int permits) {
     setState(permits);
 }

可以看到传入的许可证数量就是AQS中的state变量,并且只有一个Sync属性。

·acquire()

 public void acquire() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
 }
 ​
 public final void acquireSharedInterruptibly(int arg)
     throws InterruptedException {
     if (Thread.interrupted())
         throw new InterruptedException(); //如果被中断了,那么直接抛出中断异常
     if (tryAcquireShared(arg) < 0) //尝试拿锁
         doAcquireSharedInterruptibly(arg);//拿锁失败
 }
 ​
 protected int tryAcquireShared(int acquires) {
     return nonfairTryAcquireShared(acquires);
 }
 ​
 //尝试申请一个信号量
 final int nonfairTryAcquireShared(int acquires) {
     for (;;) { //死循环
         int available = getState();//保存可用信号的快照
         int remaining = available - acquires;//保存剩余可用信号的快照
         if (remaining < 0 || //如果剩余数量<0 
             compareAndSetState(available, remaining)) //cas将可用数量修改为剩余数量
             return remaining;//返回剩余数量
     }
 }
 ​
 //申请失败-doAcquireSharedInterruptibly()
 可以看到该代码似曾相识~~
 private void doAcquireSharedInterruptibly(int arg)
     throws InterruptedException {
     final Node node = addWaiter(Node.SHARED);//将该节点封装为共享节点到竞争队列中去
     boolean failed = true;
     try {
         for (;;) { //死循环
             final Node p = node.predecessor();//拿到当前节点的前驱节点
             if (p == head) {//如果前驱节点为头节点,那么尝试获取信号量
                 int r = tryAcquireShared(arg);//尝试获取信号量,返回值是剩余可用的信号量
                 if (r >= 0) {//在读写锁中提过:在读写锁中如果获取锁成功,那么这里永远返回1,但是在信号量中这里可能返回0,也即没有可用的信号量了,既然没有可用的信号量了,那么为什么还需要继续去唤醒后面的线程呢?这里修改为r>0不好吗?
                     setHeadAndPropagate(node, r);//将自己设置为头节点,并且唤醒后续等待线程
                     p.next = null; // help GC
                     failed = false;
                     return;
                 }
             }
             //否则前面不是头节点或者获取信号量失败,那么在阻塞前需要将前驱节点的状态设置为-1,然后再去阻塞
             if (shouldParkAfterFailedAcquire(p, node) &&
                 parkAndCheckInterrupt())
                 throw new InterruptedException();
         }
     } finally {
         if (failed)//如果失败了,则做取消操作
             cancelAcquire(node);
     }
 }
 ​

·release()

 public void release() {
     sync.releaseShared(1);
 }
 ​
 public final boolean releaseShared(int arg) {
     if (tryReleaseShared(arg)) { //释放信号量,即将信号量+1
         doReleaseShared();//唤醒后续线程
         return true;
     }
     return false;
 }
 ​
 protected final boolean tryReleaseShared(int releases) {
     for (;;) {
         int current = getState();
         int next = current + releases;
         if (next < current) // overflow
             throw new Error("Maximum permit count exceeded");
         if (compareAndSetState(current, next))
             return true;
     }
 }
 ​
 private void doReleaseShared() {
     for (;;) {
         Node h = head;
         if (h != null && h != tail) {
             int ws = h.waitStatus;
             if (ws == Node.SIGNAL) {
                 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                     continue;            // loop to recheck cases
                 unparkSuccessor(h);
             }
             else if (ws == 0 &&
                      !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                 continue;                // loop on failed CAS
         }
         if (h == head)                   // loop if head changed
             break;
     }
 }

可以看到该类就是一把有数量限制的共享锁而已,其方法上面均分析过,但是在这里需要分析的一点就是为什么在知道剩余信号量为0的时候还需要去唤醒后续线程呢?并且在AQS中为什么要引入PROPAGATE(传播)这一个标识呢?

·jdk 1.6的BUG?

关于setHeadAndPropagate()的代码和releasedShared()在jdk1.6的时候是存在一个bug的。在1.6的时候其代码如下:

 private void setHeadAndPropagate(Node node, int propagate) {
     setHead(node);
     if (propagate > 0 && node.waitstatus != 0) {
         Node s = node.next;
         if (s == null || s.isShared())
             unparkSuccessor(node);
     }
 }
 ​
 public final boolean releaseShared(int arg) {
     if (tryReleaseShared(arg)) {
         Node h = head;
         if (h != null && h.waitStatus != 0)
             unparkSuccessor(h);
         return true;
     }
     return false;

关于该bug会造成的问题在jdk官网也有说明:

A DESCRIPTION OF THE PROBLEM : Semaphore initial state 0. 4 threads run 4 tasks. Two threads run acquire semaphore once, the other two threads run release semaphore once. One possible result is the semaphore state value is 1 and one thread still waiting.

The possible reason: When AbstractQueuedSynchronizer#release are called, head.waitStatus may be 0 because the previous acquire thread may run at AbstractQueuedSynchronizer#doAcquireShared before setHeadAndPropagate is called.

·官网给出的可能会出现bug的代码示例:

 public class TestSemaphore {
 ​
     private static Semaphore sem = new Semaphore(0);
 ​
     private static class Thread1 extends Thread {
         @Override
         public void run() {
             sem.acquireUninterruptibly();
         }
     }
 ​
     private static class Thread2 extends Thread {
         @Override
         public void run() {
             sem.release();
         }
     }
 ​
     public static void main(String[] args) throws InterruptedException {
         for (int i = 0; i < 10000000; i++) {
             Thread t1 = new Thread1();
             Thread t2 = new Thread1();
             Thread t3 = new Thread2();
             Thread t4 = new Thread2();
             t1.start();
             t2.start();
             t3.start();
             t4.start();
             t1.join();
             t2.join();
             t3.join();
             t4.join();
             System.out.println(i);
         }
     }
 }

该代码的场景是:初始化信号量为0,然后t1,t2去获取信号量,然后t3,t4去释放信号量。接下来就来分析一下为什么会产生bug?

无标题网络拓扑图(10) 枫叶云笔记

7.CountDownLatch

减少计数的阀门,允许一个多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。

·示例

 @Slf4j
 public class CountDownLatchTest {
     public static void main(String[] args) throws InterruptedException {
         CountDownLatch countDownLatch = new CountDownLatch(3);
         for (int i = 0; i < 3; i++) {
             new Thread(() -> {
                 log.debug("{}--开始执行", Thread.currentThread().getName());
                 sleep(2);
                 log.debug("{}--执行结束", Thread.currentThread().getName());
                 countDownLatch.countDown();
 ​
             }, "thread-" + i).start();
         }
         log.debug("{}--等待子线程执行完毕", Thread.currentThread().getName());
         countDownLatch.await();
         log.debug("{}--开始执行", Thread.currentThread().getName());
 ​
     }
 ​
 ​
     public static void sleep(int time) {
         try {
             Thread.sleep(time * 1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
 }
 //--结果
 2024-01-27 14:47:49 [thread-1] - thread-1--开始执行
 2024-01-27 14:47:49 [thread-0] - thread-0--开始执行
 2024-01-27 14:47:49 [main] - main--等待子线程执行完毕
 2024-01-27 14:47:49 [thread-2] - thread-2--开始执行
 2024-01-27 14:47:51 [thread-1] - thread-1--执行结束
 2024-01-27 14:47:51 [thread-0] - thread-0--执行结束
 2024-01-27 14:47:51 [thread-2] - thread-2--执行结束
 2024-01-27 14:47:51 [main] - main--开始执行
 可以看到只有当3个线程执行完毕后主线程才能够开始执行
 那么很容易想到,当主线程调用await()的时候一定去阻塞了,那么之后又能够工作,说明被唤醒了,那么是由哪个线程来唤醒的呢?

·类属性

 public CountDownLatch(int count) {
     if (count < 0) throw new IllegalArgumentException("count < 0");
     this.sync = new Sync(count);
 }
 ​
 private static final class Sync extends AbstractQueuedSynchronizer {
     private static final long serialVersionUID = 4982264981922014374L;
 ​
     Sync(int count) {
         setState(count);
     }
 ​
     int getCount() {
         return getState();
     }
 ​
     protected int tryAcquireShared(int acquires) {
         return (getState() == 0) ? 1 : -1;
     }
 ​
     protected boolean tryReleaseShared(int releases) {
         // Decrement count; signal when transition to zero
         for (;;) {
             int c = getState();
             if (c == 0)
                 return false;
             int nextc = c-1;
             if (compareAndSetState(c, nextc))
                 return nextc == 0;
         }
     }
 }
 }

可以看到CountDownLatch的内部实现是由一个Sync实现的,只要是Sync那就是AQS实现的。

·await()

 public void await() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
 }
 ​
 ​
 public final void acquireSharedInterruptibly(int arg)
     throws InterruptedException {
     if (Thread.interrupted())
         throw new InterruptedException();
     if (tryAcquireShared(arg) < 0) //获取”锁“的逻辑
         doAcquireSharedInterruptibly(arg);
 }
 ​
 /*
     如果state=0,那么意味着当前线程所等待的线程都执行完毕了,那么我不用阻塞,返回1即获取”锁“成功
     如果state!=0,那么代表着当前线程还需要等待其它线程完成后才能执行,也即返回-1,即获取”锁“失败
     所以在上面的例子中,主线程调用await()后会被阻塞,因为前面的3个线程还没有执行完
 */
 protected int tryAcquireShared(int acquires) {
     return (getState() == 0) ? 1 : -1;
 }

·countDown()

 public void countDown() {
     sync.releaseShared(1);
 }
 ​
 public final boolean releaseShared(int arg) {
     /*
         尝试获取"锁",通过下面的代码可以看到如果初始state=3,
         那么state的状态变化为:t1: 3->2
                             t2: 2->1
         当t3进来的时候获取到state=1,那么将state值cas为0后,返回true,也即会进入到doReleaseShared()中,
         然后去唤醒因为await()而阻塞的线程。
         也就是说将state变量cas为0的那个线程将会唤醒阻塞的线程,如果此时有4个人来countDown(),那么只要3个线程执行了,那么就会唤醒主线程(抽象一下:聚会吃饭,只要来了5个人就开桌,也就是说第5个位置只要有人了就开桌。)
     */
     if (tryReleaseShared(arg)) { 
         doReleaseShared();
         return true;
     }
     return false;
 }
 ​
 protected boolean tryReleaseShared(int releases) {
     // Decrement count; signal when transition to zero
     for (;;) {
         int c = getState(); //获取state
         if (c == 0)//如果state=0,那么返回false
             return false;
         int nextc = c-1; //否则cas将state的值减1
         if (compareAndSetState(c, nextc))
             return nextc == 0;
     }
 }
 ​
 private void doReleaseShared() {
     for (;;) {
         Node h = head;
         if (h != null && h != tail) {
             int ws = h.waitStatus;
             if (ws == Node.SIGNAL) {
                 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                     continue;            // loop to recheck cases
                 unparkSuccessor(h);
             }
             else if (ws == 0 &&
                      !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                 continue;                // loop on failed CAS
         }
         if (h == head)                   // loop if head changed
             break;
     }
 }

8.cyclicBarrier

允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

A CyclicBarrier支持一个可选的Runnable命令,每个屏障点运行一次,在派对中的最后一个线程到达之后,但在任何线程释放之前。 在任何一方继续进行之前,此屏障操作对更新共享状态很有用。

·示例

 @Slf4j
 public class CyclicBarrierTest2 {
     static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
 ​
     public static void main(String[] args) {
         for (int i = 0; i < 3; i++) {
             new Thread(() -> {
                 try {
 ​
                     log.debug("{}====begin", Utils.getThreadName());
                     cyclicBarrier.await();
                     log.debug("{}====do", Utils.getThreadName());
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }, "t-" + i).start();
             Utils.sleep(2000);
         }
 ​
     }

·结果

2024-01-29 13:24:43 [t-0] - t-0====begin 2024-01-29 13:24:45 [t-1] - t-1====begin 2024-01-29 13:24:47 [t-2] - t-2====begin 2024-01-29 13:24:47 [t-2] - t-2====do 2024-01-29 13:24:47 [t-0] - t-0====do 2024-01-29 13:24:47 [t-1] - t-1====do

可以看到当3个线程都到达的时候才会一起执行下去。

·属性

 private static class Generation {
     boolean broken = false;
 }
 ​
 private final ReentrantLock lock = new ReentrantLock();//互斥锁
 ​
 private final Condition trip = lock.newCondition();//条件变量
 ​
 private final int parties;//需要到达屏障的线程数
 ​
 private final Runnable barrierCommand;//当所有线程都到达后执行的回调操作
 ​
 private Generation generation = new Generation();//当前generation
 ​
 private int count;//表示还有多少线程没有到达屏障
 ​
 public CyclicBarrier(int parties, Runnable barrierAction) {
     if (parties <= 0) throw new IllegalArgumentException();
     this.parties = parties;//赋值
     this.count = parties;//初始时未到达的线程就等于需要到达的线程(和Phaser很像)
     this.barrierCommand = barrierAction;//回调操作
 }

可以看到该类没有直接通过AQS,而是使用了ReentLock和一个条件变量来实现的逻辑。

·await()

 public int await() throws InterruptedException, BrokenBarrierException {
     try {
         return dowait(false, 0L);
     } catch (TimeoutException toe) {
         throw new Error(toe); // cannot happen
     }
 }
 ​
 private int dowait(boolean timed, long nanos)
     throws InterruptedException, BrokenBarrierException,
 TimeoutException {
     final ReentrantLock lock = this.lock;
     lock.lock();//上锁
     try {
         final Generation g = generation;//拿到当前代
 ​
         if (g.broken)//如果当前代已经被破坏了,那么就抛出异常
             throw new BrokenBarrierException();
 ​
         if (Thread.interrupted()) {//如果已经被中断,那么执行下面的逻辑
             breakBarrier();
             throw new InterruptedException();//抛出中断异常
         }
 ​
         int index = --count;//未到达线程数-1
         if (index == 0) {//如果未达到线程数为1,那么说明这是当前代线程组中的最后一个线程了,那么应该唤醒其它线程
             boolean ranAction = false;
             try {
                 final Runnable command = barrierCommand;
                 if (command != null)//先执行回调任务
                     command.run();
                 ranAction = true;
                 /*
                       private void nextGeneration() {
                             trip.signalAll();//将在等待队列中的线程移到同步队列中去并且唤醒阻塞线程
                             count = parties;//将未到达线程重置
                             generation = new Generation();//创建新一代
                         }                
                 */
                 nextGeneration();//唤醒阻塞线程,重置未到达线程数,创建下一代
                 return 0;
             } finally {
                 if (!ranAction)//如果执行失败了,那么设置当前代被破坏,重置未到达线程数,唤醒其它阻塞线程
                     breakBarrier();
             }
         }
 ​
         for (;;) {
             try {
                 if (!timed)//如果没有设置超时
                     trip.await();//直接去条件队列中等待
                 else if (nanos > 0L)
                     nanos = trip.awaitNanos(nanos);
             } catch (InterruptedException ie) {//如果捕捉到中断异常,那么判断是否是当前代和当前代是否被破坏
                 if (g == generation && ! g.broken) {//如果代没有被改变并且也没有被破坏,那么在这里设置当前代被破坏,重置未到达线程数,唤醒其它阻塞的线程,然后抛出异常
                     breakBarrier();
                     throw ie;
                 } else { //否则,也即代被改变了(有某个线程调用了nextGeneration()修改了代,但是此刻改线程又被中断唤醒了,那么这意味着nextGeneration()先于中断执行,那么这个时候认为此次中断无效,在这里恢复中断标志位,让线程继续执行)
                     Thread.currentThread().interrupt();
                 }
             }
 ​
             if (g.broken) //然后继续判断当前代是否被破坏,若是则抛出异常
                 throw new BrokenBarrierException();
 ​
             if (g != generation)//如果代被改变了,返回还未到达的线程数量
                 return index;
 ​
             if (timed && nanos <= 0L) {//超时处理
                 breakBarrier();
                 throw new TimeoutException();
             }
         }
     } finally {//最后解锁
         lock.unlock();
     }
 }

·breakBarrier()

 private void breakBarrier() {
     generation.broken = true; //设置当前代被破坏了
     count = parties;//重置未到达的线程数
     trip.signalAll();//唤醒所有在等待队列中阻塞的线程
 }

·示例

 public class CyclicBarrierTest2 {
 ​
     static CyclicBarrier c = new CyclicBarrier(3);
 ​
     public static void main(String[] args) throws Exception {
 ​
         Thread t1 = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     long start = System.currentTimeMillis();
                     System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
                     c.await();
                     System.out.println("子线程" + Thread.currentThread().getName() + " 执行完成 " + (System.currentTimeMillis() - start));
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         });
 ​
 ​
         Thread t2 = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     long start = System.currentTimeMillis();
                     long time = 5000;
                     System.out.println("子线程" + Thread.currentThread().getName() + "开始执行,睡眠时间 " + time);
                     Thread.sleep(time);
                     c.await();
                     System.out.println("子线程" + Thread.currentThread().getName() + " 执行完成 " + (System.currentTimeMillis() - start));
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         });
 ​
 ​
         Thread t3 = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     long start = System.currentTimeMillis();
                     System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
                     c.await();
                     System.out.println("子线程" + Thread.currentThread().getName() + " 执行完成 " + (System.currentTimeMillis() - start));
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         });
 ​
         t1.start();
         t2.start();
         t3.start();
         Thread.sleep(100);
         t3.interrupt();
 ​
     }
 }
 ​
 //结果
 子线程Thread-0开始执行
 子线程Thread-1开始执行,睡眠时间 5000
 子线程Thread-2开始执行
 java.util.concurrent.BrokenBarrierException
     at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
     at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
     at com.wjcoder.juc.CyclicBarrierTest$1.run(CyclicBarrierTest.java:19)
     at java.lang.Thread.run(Thread.java:745)
 java.lang.InterruptedException
     at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
     at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
     at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)
     at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
     at com.wjcoder.juc.CyclicBarrierTest$3.run(CyclicBarrierTest.java:51)
     at java.lang.Thread.run(Thread.java:745)
 java.util.concurrent.BrokenBarrierException
     at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
     at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
     at com.wjcoder.juc.CyclicBarrierTest$2.run(CyclicBarrierTest.java:36)
     at java.lang.Thread.run(Thread.java:745)
 ​
 //原因分析
 首先T1执行await(),进入等待队列中阻塞,然后T2开始运行并且睡眠5s--
 然后T3执行await()方法,进入等待队列中阻塞。
 此时T2还在阻塞中(5s),然后过了100ms,t3调用了interrupt()中断t3,使得t3的阻塞失效,
 从await()中醒来,然后执行checkInterruptWhileWaiting(),将T3节点放入到同步队列中去,并且抢锁
 并且抛出interruptedException异常,然后返回到await()方法中,被catch{}捕获,
 然后判断g == generation && ! g.broken,此时这两个条件都为true,然后执行breakBarrier(),
 将generation设置为true,并且重置栅栏数,和调用signalAll()将T1也放入到同步队列中去,
 然后执行finally{},释放锁调用unlock()唤醒T1
 此时T1被唤醒,抢锁成功,然后从park()醒来,因为没被中断,所以下面的代码正常执行,
 然后返回到await()方法,继续往下执行:if (g.broken):因为T3已经将g.broken设置为true,
 所以这里会抛出BrokenBarrierException()异常,也就是第二个异常,最后释放锁,
 然后此刻T2过了5s后醒来,进入到await(),先拿锁,能成功,
 然后直接判断(g.broken)为true,直接抛出异常,然后释放锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值