Skip to content

jdk concurrent aqs

wangkaiwu edited this page Apr 5, 2022 · 15 revisions

Jdk 中的 AbstractQueuedSynchronizer

结构体

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
        implements java.io.Serializable {
    // 双向不循环链表
    private transient volatile Node head;
    private transient volatile Node tail;
    private volatile int state;
}
static final class Node {
    static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;
    // 标记当前节点是否失效
    static final int CANCELLED = 1;
    // 千万要注意,这个状态不是本节点在等待信号量,而是 next 节点在等待信号量!!!
    static final int SIGNAL = -1;
    static final int CONDITION = -2;
    // 只作用于 head 节点,用于表示当前有可用的信号量
    static final int PROPAGATE = -3;
    // waitStatus == 0,也不是用来表示本节点的状态,而是表示后续节点无需被唤醒
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
}

acquire

public abstract class AbstractQueuedSynchronizer {
    public final void acquire(int arg) {
        // - 尝试请求 arg 个信号量
        // - 添加到等待队列
        // - 尝试获取信号量,获取不到就会 park,之后等待被唤醒。
        // - 无论是什么原因被唤醒,都会重试获取信号量。
        // - 如果获取锁之前曾经被中断过,则进行
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
}

addWaiter

public abstract class AbstractQueuedSynchronizer {
    // 请注意该方法唯一的 return 位置。
    // 只有原子的将自己放到队尾才会返回。
    // 该方法存在一个竟态条件 tail 的值
    private Node addWaiter(Node mode) {
        Node node = new Node(mode);
        for (; ; ) {
            // 尾节点
            Node oldTail = tail;
            if (oldTail != null) {
                // 等待队列已经初始化过了
                // 等同于 node.setPrev(oldTail),之所以不直接set,因为 prev 变量是 volatile
                // 由于下一行指令本身就涉及编译器和内存双屏障,所以这里就相当于省掉了一次屏障.
                // ps: 换成 AtomicReference 就自带 lazySet 方法了
                node.setPrevRelaxed(oldTail);
                // cas 地将自己作为 tail
                if (compareAndSetTail(oldTail, node)) {
                    // 既然自己成了 tail,就把自己加到 旧的 tail 后面
                    oldTail.next = node;
                    return node;
                }
            } else {
                // 初始化
                initializeSyncQueue();
            }
        }
    }

    // 所谓的初始化,就是将 head 和 tail 设置为空节点。
    // 也可以认为head 和 tail 各有一个哨兵。
    // 哨兵无法 park 和 unpark,因为有关联线程。
    // 哨兵初始 waitStatus 是 0
    private final void initializeSyncQueue() {
        Node h;
        if (HEAD.compareAndSet(this, null, (h = new Node())))
            tail = h;
    }
}

acquireQueued

public abstract class AbstractQueuedSynchronizer {
    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (; ; ) {
                // 等同于 p = node.prev();
                final Node p = node.predecessor();
                // 时刻要注意一点,head 有可能是哨兵。
                // 如果 head 不是哨兵,则一定是一个获取到信号量的节点。
                // 判定 p==head 不会存在多线程问题,理由是只有一个节点的 prev 会是 head 节点。
                // 因为从后往前遍历不会存在多线程问题, addWaiter 方法可以保证这一点
                if (p == head && tryAcquire(arg)) {
                    // 如果信号量获取成功,则完全可以将自己作为头节点。
                    // - head 如果是哨兵,替换掉哨兵无可厚非
                    // - head 如果是其他获取到信号量的线程。它都已经获取到信号了,也可以被替换掉了。
                    setHead(node);
                    // 旧的 head 节点已经没用了。
                    p.next = null; // help GC
                    return interrupted;
                }
                // 走到这里有两种可能:
                // 1. 还没轮到自己获取信号量,node.prev 不是 head
                // 2. 信号量不够用。tryAcquire 失败
                if (shouldParkAfterFailedAcquire(p, node)) {
                    // 如果 p 的状态恰好是 SIGNAL 则执行
                    interrupted |= parkAndCheckInterrupt();
                }
                // 只要 prev 不是 SIGNAL 状态,都会重试
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }

    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

    // 检查并更新未能获取的节点的状态。
    // 如果线程应该阻塞,则返回 true。
    // 这是所有采集循环中的主要信号控制。
    // 要求 pred == node.prev。
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL) {
            // 由于 pred.waitStatus 标记的是下一个节点的状态,
            // 说明 node 已经处于了等待获取信号量状态
            // 可以直接 park
            return true;
        }
        // CANCELLED =  1. 只有取消状态 > 0
        // CANCELED 表示的是节点自身的状态,说明这个节点需要移除
        if (ws > 0) {
            // 说明 pred 应该要从等待队列移除
            do {
                // A -> B -> C。 B 是 pred, C 是node
                // 执行后, A -> C。 也就是把 B 移除了。
                // 由于只修改了 node.prev,所以 node.next 其实还是旧值
                // 换句话说,想要遍历必须从后往前开始, 从 tail -> head。绝不可以 head -> tail
                node.prev = pred = pred.prev;
                // 现在的 pred 是 A。意思是看下 A 是否也需要移除
                // 但是 pred 的尽头是 head,而 head 有两种可能
                // - 哨兵:哨兵的状态永远是 <= 0,不可能处于 CANCELD 状态,没有意义。
                // - 已经获取到信号量的节点:都已经获取到信号量了,在设置为 CANCELED 还有什么意义呢。
                // 综上因为哨兵的存在,所以最后一个 pred 的值肯定 <=0。精髓啊 !!!
            } while (pred.waitStatus > 0);
            // do-while 的作用就是移除失效节点,
            // 跳出循环的 pred 是离 node 最近的一个有效节点。
            // 通过下面的方法完成对失效节点的彻底删除
            pred.next = node;
            // 既然前面有节点被移除了,可以再尝试一下是否能成功获取信号量
        } else {
            // 如果 pred 当前状态 == 0,而自己就是因为获取不到信号量才来到的这里
            // 所以需要把前一个节点的 waitStatus 改为 SIGNAL,表示自己在等待获取信号
            // 是的,SIGNAL 的作用是用来标记下一个节点需要信号量
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
            // 还会再尝试获取一次信号量
        }
        return false;
    }

    private final boolean parkAndCheckInterrupt() {
        // 阻塞线程
        LockSupport.park(this);
        // 被唤醒了。可能是被 release 唤醒,也可能是被 interrupt 唤醒
        return Thread.interrupted();
    }
}

release

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
        implements java.io.Serializable {
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            // 友情提醒:head 可能是哨兵,也可能是 release 的调用者。。。。
            // 这里的条件很有意思:
            // h != null: 只要没有发生竟态,head 就会一直为空。从侧面来讲,也不存在等待者
            // h.waitStatus != 0: 由此可知,waitStatus == 0 表示后续节点无需被唤醒
            //    head.waitStatus == 0: 表示的是等待队列的节点都无需唤醒,直白点儿就是没人在等待
            if (h != null && h.waitStatus != 0) {
                unparkSuccessor(h);
            }
            // 如果 h 已经获取到了信号量,就不管了。反正 h 早晚也会释放信号量
            return true;
        }
        return false;
    }
}

unparkSuccessor

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
        implements java.io.Serializable {
    private void unparkSuccessor(Node node) {
        // 期望 node 的状态是 SIGNAL(-1)
        int ws = node.waitStatus;
        if (ws < 0) {
            // ws 可能处于 SIGNAL(-1) PROPAGATE(-3) 状态
            //  SIGNAL: 由于后面的逻辑就是为了唤醒后续节点,
            //      所以提前将状态置为后续节点无需唤醒,也就是0 
            //  PROPAGATE: 无条件传播状态 !!!
            node.compareAndSetWaitStatus(ws, 0);
        }
        // 这里探讨 node 是谁的问题
        //  - 是本人:既然是唤醒者本人,唤醒 next 也就是下一个等待者无可厚非。
        //  - 是哨兵:哨兵不管,唤醒的只能是 next,哨兵没必要唤醒。 
        //  - 是他人:他人必然也拿到信号量了,否则不会到 head 位置上来.
        //              所以也没有必要唤醒,所以更加应该唤醒 node.next
        // 后续节点
        Node s = node.next;
        // CANCELED(1) s 可能被 cancel 了。
        // 准备唤醒下一个等待者,结果这个人不存在,或者状态是 CANCELED(1)
        if (s == null || s.waitStatus > 0) {
            // help gc
            s = null;
            // 逐步分析, p 是尾部
            // p != node && p != null 这可以转化成 p != null && p != node
            // p 如果等于空,就表示队列是空的,因为 p 是队尾。
            // p 如果等于 node,而 node 是 head,也就是判断一下队列是否只有 p 这一个节点
            // p 的 waitStatus <= 0,说明这个节点的状态正常
            // p = p.prev : p 是队尾,p.prev 是队尾的前一个节点。
            // 翻译一下:就是从队尾开始往前遍历,找一个最靠前的状态正常的节点,然后将其唤醒
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        // 只要节点存在就唤醒
        if (s != null)
            LockSupport.unpark(s.thread);
    }
}

acquire & release 总结

情况一

count = 1,A 请求 1 信号量, B 请求 1 信号量

state count head tail queue 备注
init 1 null null
A acquire 1 0 null null
B acquire 1 0 None None None(0) <-> None(0)
B park 0 None B None(S)<->B(S)
A release 1 1 None B None(S)<->B(S)
A unpark B 1 None B None(S)<->B(0) B 的 next 还是 B,所以 A 唤醒 B
B acquire 1 0 B B B(0)<-> B(0) ; None(0)-> null
B release 1 1 B B B(0)<- B(0) ; B(0)-> null

acquireShared

public abstract class AbstractQueuedSynchronizer {
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0) {
            // 信号量不足
            doAcquireShared(arg);
        }
        // 获取成功
    }

    private void doAcquireShared(int arg) {
        // SHARED = new Node();
        // node.nextWaiter = Node.SHARED;
        final Node node = addWaiter(Node.SHARED);
        boolean interrupted = false;
        try {
            for (; ; ) {
                // 上一个节点
                final Node p = node.predecessor();
                // head 可能是哨兵,也可能是已获得信号量的节点 
                if (p == head) {
                    // r 是扣除 arg 后剩余信号量
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 信号量获取成功, 将自己作为 head 节点
                        // 这里非常像是循环唤醒。以 None -> A -> B -> C 为例。
                        // A 被唤醒,获取了信号量,那么就会调用唤醒函数把 head.next 唤醒,也就是 B。
                        // 如果 B 被A唤醒了,会以同理唤醒 C。直到无法唤醒为止。
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        return;
                    }
                }
                // 只要自己不是 head —> next 节点,就会直接 park
                if (shouldParkAfterFailedAcquire(p, node)) {
                    // 如果头节点是哨兵,而自己有没有获得信号量,所以 park
                    // 如果前一个不是头结点,也需要 park.
                    // 因为会有 head + 1 节点来保证吧自己唤醒
                    interrupted |= parkAndCheckInterrupt();
                }
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        } finally {
            if (interrupted)
                selfInterrupt();
        }
    }
}

setHeadAndPropagate

public abstract class AbstractQueuedSynchronizer {
    // 此方法是获取信号量成功后调用,
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head;
        // 按照惯例,获取信号量成功后将自己作为头节点
        // head 原本是哨兵:既然是哨兵,就需要顶掉哨兵,自己当头节点
        // head 是获取了信号量的节点:既然前一个 head 已经拿到信号量了,也会顶掉,因为不再用了
        setHead(node);

        if (propagate > 0 ||  // 还有空闲的信号量
                h == null ||  // 没有可用信号量,但是原本的头节点也不存在。自己成为头节点之前不存在头结点
                h.waitStatus < 0 ||  // 原本的头结点也存在。但是 h 的 next 在等待信号量
                (h = head) == null || // 原本的头节点显示没有人在等待信号,那就再拿一下最新的头结点(我认为此时为空的可能性极小)
                h.waitStatus < 0) { // 最新的头结点的状态显示,后续节点在等待信号量,或者有可用信号量
            Node s = node.next;
            // 因为当前是共享资源模式,所以允许同时有多个线程获取信号量
            if (s == null || s.isShared()) {
                // 如果 node 后面没有节点了,或者是共享节点,就尝试唤醒一下
                // 为何 s == null 还需要唤醒??? 
                doReleaseShared();
            }
        }
    }
}

releaseShared

public abstract class AbstractQueuedSynchronizer {
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            // 信号量释放成功
            doReleaseShared();
            return true;
        }
        return false;
    }

    private void doReleaseShared() {
        for (; ; ) {
            Node h = head;
            if (h != null && h != tail) {
                // 只要队列中不止一个元素就进来
                int ws = h.waitStatus;
                // ws == SIGNAL,表示后续节点在等待信号量
                if (ws == Node.SIGNAL) {
                    // 后面就会唤醒后续节点了,所以先把状态改成后续节点无需被唤醒
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0)) {
                        // 没能把 head 的状态改回来
                        continue;            // loop to recheck cases
                    }
                    // unpark 的主要作用就是唤醒 h.next
                    unparkSuccessor(h);
                } else if (ws == 0 &&
                        !h.compareAndSetWaitStatus(0, Node.PROPAGATE)) {
                    // ws == 0: 表示后续节点无需唤醒
                    // 将状态改为 PROPAGATE,主要是服务于 setHeadAndPropagate
                    // 对于 doReleaseShared 方法而言,有没有 PROPAGATE 状态无区别
                    continue;                // loop on failed CAS
                }
                // 走到这里,要么执行了 if 中的 unpark 唤醒了后续节点
                // 要么是在 else if 中把状态改为了 PROPAGATE
            }
            // 如果 head 变化就再次循环
            if (h == head)                   // loop if head changed
                break;
        }
    }
}

关于 PROPAGATE 状态

设想一个场景,release 的时候发现等待队列至少有一个等待者。 而 head.next 这个等待者却没有将 head 的状态置为 SIGNAL。 这有两种可能:

  • 等待者还没来得及将状态设为 SIGNAL。这个情况没有关系,因为无论如何都会重试。
  • 刚释放完信号量等待者(head.next)已经拿到了信号量。

通用方法

compareAndSetTail

public abstract class AbstractQueuedSynchronizer {
    private final boolean compareAndSetTail(Node expect, Node update) {
        // 等同于 oldTail = tail;
        //       if tail == expect; 
        //          tail = update; 
        //       return oldTail;
        return TAIL.compareAndSet(this, expect, update);
    }
}

node 方法

static final class Node {
    volatile int waitStatus;

    final Node predecessor() {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    final boolean compareAndSetWaitStatus(int expect, int update) {
        // 等同于 waitStatus.cas(expect, update);
        return WAITSTATUS.compareAndSet(this, expect, update);
    }
}

LockSupport

park

public class LockSupport {
    private static final Unsafe U = Unsafe.getUnsafe();
    private static final long PARKBLOCKER = U.objectFieldOffset(Thread.class, "parkBlocker");

    private static void setBlocker(Thread t, Object arg) {
        // 等价于 t.setParkBlocker(arg);
        // 由于 Thread 类的 parkBlocker 成员是 volatile 修饰的,然儿这里不想让编译器产生一个写屏障影响性能,所以就用了 unsafe 的方式调用。
        // volatile Object parkBlocker;
        U.putObject(t, PARKBLOCKER, arg);
    }

    // blocker 对应的是 AQS 对象本身。方法的作用是让当前线程休眠
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        // == t.setParkBlocker(blocker); blocker 的主要作用是给 jfr 提供数据。
        setBlocker(t, blocker);
        // false 表示后面的时间不是绝对值,0L 表示不超时
        U.park(false, 0L);
        setBlocker(t, null);
    }
}

Thread 方法

interrupted

public
class Thread implements Runnable {
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    private native boolean isInterrupted(boolean ClearInterrupted);
}

interrupt

public class Thread implements Runnable {
    private volatile Interruptible blocker;
    private final Object blockerLock = new Object();

    public void interrupt() {
        if (this != Thread.currentThread()) {
            checkAccess();
            synchronized (blockerLock) {
                // 目前看不出来在哪里赋值的
                Interruptible b = blocker;
                if (b != null) {
                    interrupt0();  // set interrupt status
                    b.interrupt(this);
                    return;
                }
            }
        }
        // 唤醒线程的一切 park() 状态
        interrupt0();
    }

    private native void interrupt0();
}

Clone this wiki locally