JAVA并发基础, 从源码分析清楚AbstractQueuedSynchronizer

在日常开发的过程中,java.util.concurrent(J.U.C)是我们在并发编程中用的最多的,如果我们想熟练的掌握JAVA并发基础,少不了需要了解 AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是 Java 并发包的基础工具类,是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。 

当然这篇文章是我自己对AQS的理解,有错误之处,请评论区留言交流。

并发基础之——AbstractQueuedSynchronizer的类结构

//比较重要的属性都在这里
// 头结点,我是把它当成当前持有锁的线程
private transient volatile Node head;

// 阻塞队列的尾节点,每个新的等待线程节点进来都插入到最后
private transient volatile Node tail;

// 代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
// 并且这个值是可以大于 1,是因为锁是可以重入的,每次重入都加上 1
private volatile int state;

// 代表当前持有独占锁的线程
private transient Thread exclusiveOwnerThread;

用图片来阐述这个结构的话如下(有点丑,管用就行):

 线程的每一个等待节点都被包装成了一个Node,Node类结构如下

static final class Node {
    static final Node SHARED = new Node();
    //标识节点当前在独占模式下
    static final Node EXCLUSIVE = null;

    //代码此线程取消了争抢这个锁
    static final int CANCELLED =  1;
    //表示当前这个node的后继节点对应的线程需要被唤醒
    static final int SIGNAL    = -1;
    //表示这个节点在条件队列,后面说
    static final int CONDITION = -2;
    //后面分析
    static final int PROPAGATE = -3;
    //
    // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待   
    // 如同我们日常买东西排队一样可以排到一半,我不想买了不排队了
    volatile int waitStatus;
    // 前驱节点的引用
    volatile Node prev;
    // 后继节点的引用
    volatile Node next;
    // 线程本身
    volatile Thread thread;

}

前面讲的都是概念,但是如果不清楚这些概念,后面可能会看不下去所以还是不要略过,下面就是真正开始分析源码了

以ReentrantLock为例子,它是我们平常用得比较多的轻量级锁,它可以分为公平锁和非公平锁,并且它是可重入的,ReentrantLock 在内部用了内部类 Sync 来管理锁,所以真正的获取锁和释放锁是由 Sync 的实现类来控制的。因为ReentrantLock可以分为公平锁和非公平锁,可以再构造ReentrantLock的时候指定

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

下面我们从非公平锁的实现 FairSync切入源码

ReentrantLock加锁 (公平锁 FairLock)

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    //加锁
    final void lock() {
        acquire(1);
    }

    public final void acquire(int arg) { 
        // 首先调用tryAcquire(1)一下,顾名思义,先尝试加锁
        // 因为此时有可能没有线程竞争有可能直接就成功了呢,也就不需要进等待队列排队了
        // tryAcquire(1)没有成功,这个时候需要把当前线程挂起,放到阻塞队列中。
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
              selfInterrupt();
        }
    }

    // 尝试直接获取锁,返回值是boolean,代表是否获取到锁
    // 返回true:1.没有线程在等待锁;2.重入锁
    // 返回false 没有抢到锁 
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        // c == 0 表名此时此刻没有线程持有锁
        if (c == 0) {
            // 虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到,
            // 调用hasQueuedPredecessors()看看有没有别人在队列中等待
            if (!hasQueuedPredecessors() &&
                // 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,
                // 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了
                compareAndSetState(0, acquires)) {
                // 到这里就是获取到锁了,标记一下现在是当前这个想成占用了锁
                setExclusiveOwnerThread(current);
                //返回 
                return true;
            }
        }
        // 会进入这个分支,说明当前是有线程占用锁的,
        // 但是会不会是同一个线程重入了,如果是重入的线程直接state+1
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
    }



    // 假设tryAcquire(arg) 返回false,那么代码将执行:
    // acquireQueued(addWaiter(Node.EXCLUSIVE), arg),
    // 此方法的作用是把线程包装成node,同时进入到队列中
    // 参数mode此时是Node.EXCLUSIVE,代表独占模式
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 以下几行代码想把当前node加到链表的最后面去,也就是进到阻塞队列的最后
        Node pred = tail;
        if (pred != null) { 
            // 将当前的队尾节点,设置为自己的前驱 
            node.prev = pred; 
            // 用CAS把自己设置为队尾, 如果成功了,tail == node
            // 这个节点成为阻塞队列新的尾巴
            if (compareAndSetTail(pred, node)) { 
             // 进到这里说明设置成功,当前node==tail, 将自己与之前的队尾相连,
             // 上面已经有 node.prev = pred,加上下面这句,也就实现了和之前的尾节点双向连接了
                pred.next = node;
                // 线程入队了,可以返回了
                return node;
            }
        }
        // 仔细看看上面的代码,如果会到这里,
        // 说明 (pred = tail)==null(队列是空的) 或者 CAS失败(有线程在竞争入队)
        enq(node);
        return node;
    }

    // 能走到这个方法只有两种可能:等待队列为空,或者有线程竞争入队,
    // 这里会自旋配合CAS设置将自己设置为tail,竞争一次竞争不到,我就多次竞争,总会排到的
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 之前说过,队列为空也会进来这里
            if (t == null) { 
                // 一开始 head 和 tail都是 null 的
                // 还是一步CAS,你懂的,现在可能是很多线程同时进来呢
                if (compareAndSetHead(new Node()))
                    // 给后面用:这个时候head节点的waitStatus==0
                    // 这个时候有了head,但是tail还是null,设置一下,
                    // 把tail指向head,放心,马上就有线程要来了,到时候tail就要被抢了
                    // 设置完了以后,继续for循环,下次就到下面的else分支了
                    tail = head;
            } else {
                // CAS设置自己为队尾,没有设置成功的话重复尝试直到成功
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }


    // 走完addWaiter之后,回到这段代码了,
    // 这个时候当前节点肯定是已经成功进入了等待队列的
    // if (!tryAcquire(arg) 
    //        && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
    //     selfInterrupt();

    // 如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg))返回true的话,
    // 此时将进入selfInterrupt()
    // 所以正常情况下,acquireQueued(addWaiter(Node.EXCLUSIVE), arg))应该返回false
    // 继续跟进看看吧
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                // p == head 说明当前节点是阻塞队列的第一个,因为它的前驱是head
                // 我的理解是阻塞队列其实并不包含head节点
                // head一般指的是占有锁的线程,head后面的才称为阻塞队列(个人理解,勿喷)
                // 所以这个当前节点其实可以去尝试抢一下锁
                // 为什么可以尝试去抢一下锁呢
                // 首先,它是队头,其次,当前的head有可能是刚刚初始化的node,
                // enq(node) 方法里面有提到,head是延时初始化的,而且new Node()的时候没有设置任何线程
                // 也就是说,当前的head不属于任何一个线程,所以作为队头,可以去试一试,
                // tryAcquire已经分析过了, 忘记了请往前看一下,就是简单用CAS试操作一下state
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 到这里,说明上面的if分支没有成功,要么当前node本来就不是队头,
                // 要么就是tryAcquire(arg)没有抢赢别人,继续往下看
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 什么时候 failed 会为 true???
            // tryAcquire() 方法抛异常的情况
            if (failed)
                cancelAcquire(node);
        }
    }

    // 会到这里就是没有抢到锁,判断是否需要挂起当前线程
    // 第一个参数是前驱节点,第二个参数才是代表当前线程的节点
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // 进入阻塞队列排队的线程会将他的前驱节点设为Node.SIGNAL
        // 也就是说如果自己可以抢锁了就让前面的节点提醒自己,自己则被挂起等待唤醒
        // 前驱节点的 waitStatus == -1 ,说明前驱节点状态正常,可以直接返回
        if (ws == Node.SIGNAL)
            return true;
        // 前驱节点 waitStatus大于0 ,说明前驱节点取消了排队。
        // 前面的人都取消排队了,但是你还得依赖它来唤醒呢,
        // 往前遍历总能找到一个没有取消排队的节点,来提醒自己
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 到了这里说明前驱节点的waitStatus不等于Node.SIGNAL和CANCEL
            // 那也就是只可能是0,-2,-3
            // 都没有看到有设置waitStatus的,所以每个新的node入队时,waitStatu都是0
            // 用CAS将前驱节点的waitStatus设置为Node.SIGNAL(也就是-1),让前面的提醒自己
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        // 这个方法返回 false,那么会再走一次 for 循序,
        // 然后再次进来此方法,此时会从第一个分支返回 true
        return false;
    }

    // if (shouldParkAfterFailedAcquire(p, node) &&
    //                parkAndCheckInterrupt())
    //                interrupted = true;

    // 如果shouldParkAfterFailedAcquire(p, node)返回true,
    // 就会执行parkAndCheckInterrupt():

    // 这个方法很简单,因为前面返回true,所以需要挂起线程,这个方法就是负责挂起线程的
    // 这里用了LockSupport.park(this)来挂起线程,然后就停在这里了,等待被唤醒=======
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

    // 为什么shouldParkAfterFailedAcquire(p, node)返回false的时候不直接挂起线程:
    // 是为了应对在经过这个方法后,node已经是head的直接后继节点了,还需要挂起干嘛,直接可以尝试抢锁了。
}

解锁操作(公平锁FairLock)

// 解锁操作相比加锁还是比较简单的
public void unlock() {
    sync.release(1);
}
//没什么说的,继续
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 回到ReentrantLock看tryRelease方法
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 没有抢到锁的不配解锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 是否完全释放锁
    boolean free = false;
    // 其实就是重入的问题,如果c==0,也就是说没有嵌套锁了,可以释放了,否则还不能释放掉
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

// 唤醒后继节点
// 从上面调用处知道,参数node是head头结点
private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;
    // 如果head节点当前waitStatus<0, 将其修改为0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 下面的代码就是唤醒后继节点,但是有可能后继节点取消了等待(waitStatus==1)
    // 从队尾往前找,找到waitStatus<=0的所有节点中排在最前面的
    Node s = node.next;
    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);
}
    //这个时候线程被唤醒的时候是在这里
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

    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)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //这里被唤醒了,然后继续检查前驱是不是头节点,尝试获取锁
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

至此公平锁下的加锁操作和解锁操作就都解析完了

公平锁总结

公平锁主要注意的就是下面三个方面

  1. 锁状态。成员变量 “state”,它为 0 的时候代表没有线程占有锁,可以去争抢这个锁,用 CAS 将 state 设为 state + args,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了,如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁。
  2. 线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park() 来挂起线程,用 LockSupport.unpark() 来唤醒线程。
  3. 阻塞队列。因为争抢锁的线程可能很多,但是只能有一个线程拿到锁,其他的线程都必须等待,这个时候就需要一个 足赛队列 来管理这些线程节点,AQS 用的其实就是一个链表队列,每个 node 都持有后继节点和前驱节点的引用,大家都在里面公平的排队等待加锁,只有排到了第一个才有资格去加锁。

 

加锁操作(非公平锁 NonFairLock)

上面解析了公平锁和非公平锁的加解锁,接下来是非公平锁,其实公平锁理解了,非公平锁就很好理解了还是先贴上代码吧

public ReentrantLock() {
    // 默认非公平锁
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁的 lock 方法:

static final class NonfairSync extends Sync {
    final void lock() {
        // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        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();
    if (c == 0) {
        // 这里没有对阻塞队列进行判断
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

其实从代码就可以看出公平锁和非公平锁的区别了:

非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在

tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程

在排队等待加锁,如果有则不会抢锁而是去队列里排队。

如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导

致在阻塞队列中的线程长期处于饥饿状态,所以ReentrantLock默认是非公平锁。

 

并发基础之——Condition

Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,这也是它常用的应用场景。

public class Demo {
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();
    private List<Object> produceItems = new LinkedList<Object>();
    private int itemCount = 0;
    private int fullCount = 50;
    public void produce(Object obj) {
        lock.lock();
        try {
            while (fullCount == produceItems.size()) {
                notFull.await();
            }
            produceItems.add(obj);
            System.out.println(Thread.currentThread().getName() + "生产了,当前数量" +produceItems.size());
            notEmpty.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void consume() {
        lock.lock();
        try {
            while (produceItems.size() == 0) {
                notEmpty.await();
            }
            //Ignor rmove(0)
            produceItems.remove(0);
            System.out.println(Thread.currentThread().getName() + "消费了,当前数量" +produceItems.size());
            notFull.signal();
        } catch (InterruptedException e) {
                e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

但是这两者在使用方式以及功能特性上还是有差别的。Object和Condition接口的一些对比

 Condition的定义如下

public interface Condition {

    void await() throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    void awaitUninterruptibly();

    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();

    void signalAll();
}

Condition的实现JAVA8中有两个

这里分析的是AbstractQueuedSynchronizer 中的 ConditionObject,主要的属性如下

public class ConditionObject implements Condition, java.io.Serializable {
        // 条件队列的第一个节点
        private transient Node firstWaiter;
        // 条件队列的最后一个节点
        private transient Node lastWaiter;
        // AQS有一个阻塞队列,用于保存等待获取锁的线程的队列。
        // 这里引入了另一个概念,叫条件队列(condition queue)

AQS有一个阻塞队列,用于保存等待获取锁的线程的队列;这里引入了另一个概念,叫条件队列(condition queue)

 阻塞队列和条件队列的异同和作用这里先说明,后面再根据源码论述

  1. 条件队列和阻塞队列的节点,都是 Node 的实例,因为条件队列的节点是需要转移到阻塞队列中去的;
  2. 一个 ReentrantLock 实例可以通过多次调用 newCondition() 来产生多个 Condition 实例,这里对应 condition1 和 conditionN并且ConditionObject 只有两个属性 firstWaiter 和 lastWaiter;
  3. 每个 condition 有一个关联的条件队列,如线程 1 调用 condition1.await() 方法即可将当前线程 1 包装成 Node 后加入到条件队列中,然后阻塞在这里,不继续往下执行,条件队列是一个单向链表;
  4. 调用condition1.signal() 触发一次唤醒,此时唤醒的是队头,会将condition1 对应的条件队列的 firstWaiter(队头) 移到阻塞队列的队尾,等待获取锁,获取锁后 await 方法才能返回,继续往下执行。

Condition的await方法大致逻辑

// 这个方法会阻塞,直到调用 “唤醒”方法,或线程被中断
public final void await() throws InterruptedException {
    // 方法要响应中断,在最开始就判断中断状态
    if (Thread.interrupted())
        throw new InterruptedException();

    // 添加到 condition 的条件队列中
    Node node = addConditionWaiter();

    // await() 之前,当前线程是必须持有锁的,这里肯定要释放掉
    int savedState = fullyRelease(node);

    int interruptMode = 0;
    // 这里退出循环有两种情况,之后再仔细分析
    // 1. isOnSyncQueue(node) 返回 true,即当前 node 已经转移到阻塞队列了
    // 2. checkInterruptWhileWaiting(node) != 0 会到 break,然后退出循环,代表的是线程中断
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 被唤醒后,将进入阻塞队列,等待获取锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

添加到 condition 的条件队列中

        private Node addConditionWaiter() {
            Node t = lastWaiter;
            // 如果最后一个节点取消排队了,将它清除出去
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //将线程包装成一个NOde,指定 它的 waitStatus 为 Node.CONDITION
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
        //清除取消排队的节点
        //这个代码要怎么说呢,没什么好说的多看几遍就懂了。。。。
        private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

至此线程已经被包装成了一个waitStatus为 Node.CONDITION的 Node对象并且进入了条件队列,回到await方法,第二步fullyRealease 

    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            //解锁
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            //这里解释一下为什么要这个操作
            //有这么一种场景;有一个线程不按套路出牌,没有抢到锁就直接调用await方法
            //那么在这里它走到解锁的时候肯定会到上面的else分支从而failed为true
            //这里就会把它设为取消排队,在遍历清除取消节点的时候就会把它清除出去
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //从阻塞队列的队头一直往后找
                //直到找到一个没有取消排队的节点唤醒它(因为这个时候它可以抢锁了)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

接下来是这段,正常情况下,已经进入了条件队列,这时通过自旋,如果发现自己还没到阻塞队列,那么挂起,等待被转移到阻塞队列。

            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //已经到达了这里
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);


final boolean isOnSyncQueue(Node node) {

    // 如果 waitStatus 还是 Node.CONDITION,也就是 -2,那肯定就是还在条件队列中
    // 因为移到阻塞队列的时候,node 的 waitStatus 会置为 0 (这一步在signal的时候,后续会说)
    // 如果 node 的前驱还是 null,说明肯定没有在阻塞队列(prev是阻塞队列链表中使用的)
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 如果 node 已经有后继节点 next 的时候,那肯定是在阻塞队列了
    if (node.next != null) 
        return true;

    // 下面这个方法看名字就大概能猜到从阻塞队列队尾查找Node,找到了说明已经转移到阻塞队列了
    return findNodeFromTail(node);
}

// 从阻塞队列的队尾往前遍历,如果找到,返回 true
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}

 isOnSyncQueue(node) 返回 false 的话,那么进到 LockSupport.park(this); 这里线程挂起,如果线程挂起的话,怎么才会唤醒线程继续走下去呢,答案就是调用sinal相关的方法

public final void signal() {
    // 调用 signal 方法的线程必须持有当前的独占锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //去唤醒条件队列的队头节点,因为它等得最久
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        private void doSignal(Node first) {
            do {
                // 将 firstWaiter 指向 first 节点后面的第一个,因为 first 节点马上要离开了
                // 如果除了first没有其他线程在条件队列了,那么需要将 lastWaiter 置为 null
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                //断绝和条件队列的关联关系
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

    final boolean transferForSignal(Node node) {
        //如果cas改变waitStatus状态失败了,说明该节点取消了
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        //自旋配合CAS进入阻塞队列的队尾
        Node p = enq(node);
        int ws = p.waitStatus;
        // 前驱节点取消排队了,提前唤醒线程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

这里signal方法的逻辑就完成了,后面就是线程被唤醒后的操作了,有以下三种情况会让线程唤醒继续往下执行:

  1. 常规路径。signal -> 转移节点到阻塞队列 -> 获取了锁(unpark)
  2. 线程中断。在 park 的时候,另外一个线程对这个线程进行了中断
  3. signal 的时候我们说过,转移以后的前驱节点取消了,或者对前驱节点的CAS操作失败了
            while (!isOnSyncQueue(node)) {
                //开始我们的线程停在了这里等待唤醒
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);

//线程唤醒后会去调用checkInterruptWhileWaiting(),
//这个方法的作用是判断是否在线程挂起期间发生了中断
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

//如果线程发生了中断就会进入transferAfterCancelledWait()方法,这个方法是干嘛用的,继续看
final boolean transferAfterCancelledWait(Node node) {
    // 用 CAS 将节点状态设置为 0 
    // 如果这步 CAS 成功,说明是 signal 方法之前发生的中断,因为signal的时候会将 waitStatus 设置为 0
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        // 将节点放入阻塞队列,即使中断了,依然会转移到阻塞队列
        enq(node);
        return true;
    }

    // 到这里是因为 CAS 失败,肯定是因为 signal 方法已经将 waitStatus 设置为了 0
    // signal 方法会将节点转移到阻塞队列,但是可能还没完成,这里自旋等待完成
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

//while 循环出来后,已经确定节点已经进入了阻塞队列

if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    interruptMode = REINTERRUPT;

    //acquireQueued方法,就是最开始加锁的逻辑了
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //自己的前驱是不是head是的话可以尝试加锁了
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //这里会检查线程是否被中断,中断过的都会返回true
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    //挂起线程并且返回线程是否被中断过
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

继续往下看就剩两个方法了

           //signal的时候会将节点转移到阻塞队列,有一步是 node.nextWaiter = null 断开节点和条件队列的联系。
            //这里有一种情况就是在signal之前就中断过了,并且我们知道中断过了也会转移到阻塞队列
            //这个时候是没有设置node.nextWaiter = null
            //如果有节点取消,就是在这里调用清除取消排队节点
            if (node.nextWaiter != null) 
                unlinkCancelledWaiters();
            //0:什么都不做,没有被中断过;
            //THROW_IE:await 方法抛出 InterruptedException 异常,因为它代表在 await() 期间发生了中断;
            //REINTERRUPT:重新中断当前线程,因为它代表 await() 期间没有被中断,而是 signal() 以后发生的中断
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值