剑指JUC原理-14.ReentrantLock原理

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

AQS 原理

概述

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

特点:

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取 锁和释放锁

getState - 获取 state 状态

setState - 设置 state 状态

compareAndSetState - cas 机制设置 state 状态

独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源

  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList(Monitor是在C++层面实现的,而 这里的等待队列是在Java层面实现的)

  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)

tryAcquire

tryRelease

tryAcquireShared 方法是一个尝试获取共享资源的方法

tryReleaseShared 方法是一个尝试获取共享资源的方法

isHeldExclusively 方法用于判断当前线程是否独占地持有资源

获取锁的姿势

java 复制代码
// 如果获取锁失败
if (!tryAcquire(arg)) {
 	// 入队, 可以选择阻塞当前线程 park unpark(为什么是park 和 unpark在后面介绍源码的时候会展示)
}

释放锁的姿势

java 复制代码
// 如果释放锁成功
if (tryRelease(arg)) {
 	// 让阻塞线程恢复运行
}

实现不可重入锁

自定义同步器

java 复制代码
// 独占锁  同步器类
    class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0, 1)) {
                // 加上了锁,并设置 owner 为当前线程  和monitor是一致的
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            // 由于state是volatile的,可以防止指令的重排序,所以要放置到下面 可以加写屏障
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override // 是否持有独占锁
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

自定义锁

java 复制代码
// 自定义锁(不可重入锁)
class MyLock implements Lock {

    private MySync sync = new MySync();

    @Override // 加锁(不成功会进入等待队列)
    public void lock() {
        sync.acquire(1);
    }

    @Override // 加锁,可打断
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override // 尝试加锁(一次)
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override // 尝试加锁,带超时
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override // 解锁
    public void unlock() {
        sync.release(1);
    }

    @Override // 创建条件变量
    public Condition newCondition() {
        return sync.newCondition();
    }
}

测试一下

java 复制代码
public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
                sleep(1);
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t2").start();
    }

输出

ini 复制代码
22:29:28.727 c.TestAqs [t1] - locking... 
22:29:29.732 c.TestAqs [t1] - unlocking... 
22:29:29.732 c.TestAqs [t2] - locking... 
22:29:29.732 c.TestAqs [t2] - unlocking... 

不可重入测试

如果改为下面代码,会发现自己也会被挡住(只会打印一次 locking)一个线程中加两个锁就不行,因为 默认是不可重入锁

java 复制代码
lock.lock();
log.debug("locking...");
lock.lock();
log.debug("locking...");

心得

起源

早期程序员会自己通过一种同步器去实现另一种相近的同步器,例如用可重入锁去实现信号量,或反之。这显然不 够优雅,于是在 JSR166(java 规范提案)中创建了 AQS,提供了这种通用的同步器机制。

目标

AQS 要实现的功能目标

  • 阻塞版本获取锁 acquire 和非阻塞的版本尝试获取锁 tryAcquire
  • 获取锁超时机制
  • 通过打断取消机制
  • 独占机制及共享机制
  • 条件不满足时的等待机制

设计

AQS 的基本思想其实很简单

获取锁的逻辑

java 复制代码
while(state 状态不允许获取) {
 	if(队列中还没有此线程) {
 		入队并阻塞
 	}
}
当前线程出队

释放锁的逻辑

java 复制代码
if(state 状态允许了) {
 	恢复阻塞的线程(s)
}

要点

  • 原子维护 state 状态
  • 阻塞及恢复线程
  • 维护队列
state 设计
  • state 使用 volatile 配合 cas 保证其修改时的原子性
  • state 使用了 32bit int 来维护同步状态,因为当时使用 long 在很多平台下测试的结果并不理想
阻塞恢复设计
  • 早期的控制线程暂停和恢复的 api 有 suspend 和 resume,但它们是不可用的,因为如果先调用的 resume 那么 suspend 将感知不到
  • 解决方法是使用 park & unpark 来实现线程的暂停和恢复,具体原理在之前讲过了,先 unpark 再 park 也没问题
  • park & unpark 是针对线程的,而不是针对同步器的,因此控制粒度更为精细
  • park 线程还可以通过 interrupt 打断
队列设计
  • 使用了 FIFO 先入先出队列,并不支持优先级队列
  • 设计时借鉴了 CLH 队列,它是一种单向无锁队列

队列中有 head 和 tail 两个指针节点,都用 volatile 修饰配合 cas 使用,每个节点有 state 维护节点状态

入队伪代码,只需要考虑 tail 赋值的原子性

java 复制代码
do {
 	// 原来的 tail
 	Node prev = tail;
 	// 用 cas 在原来 tail 的基础上改为 node
} while(tail.compareAndSet(prev, node))

出队伪代码

java 复制代码
// prev 是上一个节点
while((Node prev=node.prev).state != 唤醒状态) {
}
// 设置头节点
head = node;

CLH 好处:

  • 无锁,使用自旋
  • 快速,无阻塞

CLH队列本身并不会导致线程安全问题。相反,CLH队列是一种用于实现自旋锁等同步机制的数据结构,能够有效地保证线程安全性。

CLH队列(Craig, Landin, and Hagersten queue)是一种基于链表的自旋锁等待队列,它通常应用于自旋锁的实现中。在CLH队列中,每个线程都持有一个自旋锁的状态变量,并通过这些状态变量来构成一个链表结构。当一个线程需要获取锁时,它会将自己的状态设置为"已锁定",并将自己加入到队列的尾部。然后,它会等待前一个线程释放锁,并检查前一个线程的状态变量,以确定是否可以进入临界区或者继续等待。

由于CLH队列通过显式的状态变量和链表结构来组织线程的等待顺序,因此不会出现像传统锁中的竞争、饥饿等问题。CLH队列的设计使得每个线程按照严格的FIFO顺序等待锁的释放,从而确保了线程安全性。

总之,CLH队列本身并不会导致线程安全问题,它实际上是一种用于保证线程安全的同步机制。然而,在实际使用中,仍然需要注意如何正确地使用CLH队列及其相关的同步机制,以避免由于程序逻辑错误而引发的线程安全问题。

AQS 在一些方面改进了 CLH

java 复制代码
	private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 队列中还没有元素 tail 为 null
            if (t == null) {
                // 将 head 从 null -> dummy
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 将 node 的 prev 设置为原来的 tail
                node.prev = t;
                // 将 tail 从原来的 tail 设置为 node
                if (compareAndSetTail(t, node)) {
                    // 原来 tail 的 next 设置为 node
                    t.next = node;
                    return t;
                }
            }
        }
    }

首先,这段代码通过一个无限循环 for (;;) 来进行尾部节点的插入操作。

在每次循环开始时,首先获取当前的尾部节点 t = tail。

如果当前尾部节点为 null,表示队列中还没有元素,这时会尝试初始化队列,将头节点和尾节点都初始化为一个虚拟的哨兵节点(dummy node)。这个虚拟节点并不存储实际的数据,只是作为一个占位符存在,方便后续操作。

如果当前尾部节点不为 null,则将待插入节点的 prev 指针指向当前尾部节点,然后尝试使用CAS操作将尾部节点更新为待插入节点。如果CAS操作成功,表示插入成功,此时需要再次将原尾部节点的 next 指针指向新插入的节点。最后返回原尾部节点,这是因为在CLH队列中,新插入的节点的前驱节点通常需要用到。

ReentrantLock 原理

非公平锁实现原理

细看类图,这里的同步器类是抽象的,它有两个实现,看名字就知道一个是公平的,一个是非公平的

加锁流程

先从构造器开始看,默认为非公平锁实现

java 复制代码
public ReentrantLock() {
 	sync = new NonfairSync();
}

从加速 解锁流程开始看

java 复制代码
public void lock() {
        sync.lock();
}

找到非公平锁的实现

java 复制代码
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            // 下面两行代码其实在模拟自定义锁的时候都用过
            // 尝试修改锁,如果修改成功了,就会将owner线程修改
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

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

没有竞争时

第一个竞争出现时

java 复制代码
		final void lock() {
            // 下面两行代码其实在模拟自定义锁的时候都用过
            // 尝试修改锁,如果修改成功了,就会将owner线程修改
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        当出现竞争,if失败,进入else
        
        public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    	}
    	
		这里面首先调用了tryAcquire方法,其实就是尝试去加锁,但是下图所示场景,那一定是失败的,此时就会执行acquireQueued方法。

Thread-1 执行了

  • CAS 尝试将 state 由 0 改为 1,结果失败
  • 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
  • 接下来进入 addWaiter 逻辑,构造 Node 队列

图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态

Node 的创建是懒惰的

其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程

当前线程进入 acquireQueued 逻辑

java 复制代码
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;
                }
                // 如果获取不到锁,返回false,就会进入到这个if
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞

如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败

进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false

改成-1后,就有责任唤醒后继的节点

shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued (因为是一个死循环),再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败

当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true

进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

java 复制代码
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

再次有多个线程经历上述过程竞争失败,变成这个样子

解锁流程

Thread-0 释放锁,进入 tryRelease 流程,如果成功

  • 设置 exclusiveOwnerThread 为 null
  • state = 0
java 复制代码
public void unlock() {
        sync.release(1);
    }
java 复制代码
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // 判断 head不为空 且不等于0
                // 唤醒下一个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
java 复制代码
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                // 设置为 null
                setExclusiveOwnerThread(null);
            }
    		// 设置 为 0
            setState(c);
            return free;
        }

当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程

找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1

java 复制代码
private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 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;
        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)
            // 底层调用 unpark 恢复运行了
            LockSupport.unpark(s.thread);
    }

回到 Thread-1 的 acquireQueued 流程

java 复制代码
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);
        }
    }

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

之前在这里阻塞着呢,一被唤醒,就又进入循环了,进入循环后,又去执行 p == head && tryAcquire(arg)

java 复制代码
	setHead(node);
	p.next = null; // help GC
	failed = false;
	return interrupted;
  • exclusiveOwnerThread 为 Thread-1,state = 1
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
  • 原本的 head 因为从链表断开,而可被垃圾回收

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

如果不巧又被 Thread-4 占了先

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞
java 复制代码
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                // 循环过来,没竞争过,被thread4占先了!!! 返回false,又和之前一样的流程了
                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);
        }
    }

可重入原理

java 复制代码
static final class NonfairSync extends Sync {
    // ...

    // Sync 继承过来的方法, 方便阅读, 放在此处
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        // 首先先获取锁的流程
        // 先查看状态是否为 0,如果还没有获得锁,从0试图将其改成1 。如果成功,将其改为真
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
        else if (current == getExclusiveOwnerThread()) {
            // state++
            // 锁重入时,实际上是对 state做了一个累加
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

    // Sync 继承过来的方法, 方便阅读, 放在此处
    protected final boolean tryRelease(int releases) {
        // state--
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // 支持锁重入, 只有 state 减为 0, 才释放成功
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        // 返回true,才会去唤醒其他的线程
        return free;
    }
}

可打断原理

不可打断模式

在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

java 复制代码
// Sync 继承自 AQS
static final class NonfairSync extends Sync {
    // ...

    private final boolean parkAndCheckInterrupt() {
        // 如果打断标记已经是 true, 则 park 会失效
        LockSupport.park(this);
        // interrupted 会清除打断标记 // 清除了打断标记,以后线程还是能park住
        // 下次再park的时候,不受影响
        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;
                    failed = false;
                    // 还是需要获得锁后, 才能返回打断状态
                    return interrupted;
                }
                if (
                        shouldParkAfterFailedAcquire(p, node) &&
                                parkAndCheckInterrupt()
                ) {
                    // 如果是因为 interrupt 被唤醒, 返回打断状态为 true
                    interrupted = true;
                    // 只是设置为 true,并没有做任何的处理,所以还是会再次进入循环,如果获得不了锁,还是会进入这个阻塞,再次设置上park。
                    // 而这个打断标记与 Thread.interrupted(); 所控制的打断标记并不是一个东西,由于之前 返回true,并清除了打断标记,所以是可以park的。
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    public final void acquire(int arg) {
        if (
                !tryAcquire(arg) &&
                        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
            // 如果打断状态为 true
            selfInterrupt();
        }
    }

    static void selfInterrupt() {
        // 重新产生一次中断
        Thread.currentThread().interrupt();
    }
}

可打断模式

java 复制代码
static final class NonfairSync extends Sync {
    public final void acquireInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 如果没有获得到锁, 进入 ㈠
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

    // ㈠ 可打断的获取锁流程
    private void doAcquireInterruptibly(int arg) throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) {
                    // 在 park 过程中如果被 interrupt 会进入此
                    // 这时候抛出异常, 而不会再次进入 for (;;)
                    throw new InterruptedException();
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
}

公平锁实现原理

java 复制代码
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    final void lock() {
        acquire(1);
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final void acquire(int arg) {
        if (
                !tryAcquire(arg) &&
                        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
            selfInterrupt();
        }
    }
    // 与非公平锁主要区别在于 tryAcquire 方法的实现
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
            if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

    // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        // h != t 时表示队列中有 Node
        return h != t &&
                (
                        // (s = h.next) == null 表示队列中还有没有老二
                        (s = h.next) == null ||
                                // 或者队列中老二线程不是此线程
                                s.thread != Thread.currentThread()
                );
    }
}

条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

await 流程

开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程

创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部

java 复制代码
public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
   			// 将线程加入到我们条件变量中去,并且将新的node状态设置为 -2
            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);
        }

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

static final int CONDITION = -2;

接下来进入 AQS 的 fullyRelease(是有可能发生锁重入) 流程,释放同步器上的锁

java 复制代码
final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            // release方法默认一次 -1
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功

java 复制代码
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

park 阻塞 Thread-0

java 复制代码
public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
            	// 此时就park住了
                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);
        }

signal 流程

假设 Thread-1 要来唤醒 Thread-0

java 复制代码
public final void signal() {
    // 首先检查当前变量是否持有独占锁
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
    // 也不是随机调用,总是调队首
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node

java 复制代码
private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
    // 如果转移为 真,就不会进行循环了,如果转移失败,就看看还有没有下一个节点。
        }

执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的 waitStatus 改为 -1

java 复制代码
final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
    	// 将状态改为0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
    	
    	// 加入队列
    
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

Thread-1 释放锁,进入 unlock 流程,略

相关推荐
潘多编程1 小时前
Spring Boot微服务架构设计与实战
spring boot·后端·微服务
2402_857589361 小时前
新闻推荐系统:Spring Boot框架详解
java·spring boot·后端
2401_857622662 小时前
新闻推荐系统:Spring Boot的可扩展性
java·spring boot·后端
Amagi.3 小时前
Spring中Bean的作用域
java·后端·spring
侠客行03173 小时前
xxl-job调度平台之任务触发
java·后端·源码
2402_857589363 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊3 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso4 小时前
Rust 快速入门(一)
开发语言·后端·rust
sco52824 小时前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
原机小子4 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端