这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的 (6)

总的来看,AQS共享模式的运作流程和独占模式很相似,只要掌握了独占模式的流程运转,共享模式什么的不就那样吗,没难度。这也是我为什么共享模式讲解中不画流程图的原因,没必要嘛。

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

Condition

介绍完了AQS的核心功能,我们再扩展一个知识点,在AQS中,除了提供独占/共享模式的加锁/解锁功能,它还对外提供了关于Condition的一些操作方法。

Condition是个接口,在jdk1.5版本后设计的,基本的方法就是await()和signal()方法,功能大概就对应Object的wait()和notify(),Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现 ,AQS中就定义了一个类ConditionObject来实现了这个接口,

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

那么它应该怎么用呢?我们可以简单写个demo来看下效果

public class ConditionDemo { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); Thread tA = new Thread(() -> { lock.lock(); try { System.out.println("线程A加锁成功"); System.out.println("线程A执行await被挂起"); condition.await(); System.out.println("线程A被唤醒成功"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println("线程A释放锁成功"); } }); Thread tB = new Thread(() -> { lock.lock(); try { System.out.println("线程B加锁成功"); condition.signal(); System.out.println("线程B唤醒线程A"); } finally { lock.unlock(); System.out.println("线程B释放锁成功"); } }); tA.start(); tB.start(); } }

执行main函数后结果输出为:

线程A加锁成功 线程A执行await被挂起 线程B加锁成功 线程B唤醒线程A 线程B释放锁成功 线程A被唤醒成功 线程A释放锁成功

代码执行的结果很容易理解,线程A先获取锁,然后调用await()方法挂起当前线程并释放锁,线程B这时候拿到锁,然后调用signal唤醒线程A。

毫无疑问,这两个方法让线程的状态发生了变化,我们仔细来研究一下,

翻看AQS的源码,我们会发现Condition中定义了两个属性firstWaiter和lastWaiter,前面说了,AQS中包含了一个FIFO的CLH等待队列,每个Conditon对象就包含这样一个等待队列,而这两个属性分别表示的是等待队列中的首尾结点,

/** First node of condition queue. */ private transient Node firstWaiter; /** Last node of condition queue. */ private transient Node lastWaiter;

注意:Condition当中的等待队列和AQS主体的同步等待队列是分开的,两个队列虽然结构体相同,但是作用域是分开的

await

先看await()的源码:

public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 将当前线程加入到等待队列中 Node node = addConditionWaiter(); // 完全释放占有的资源,并返回资源数 int savedState = fullyRelease(node); int interruptMode = 0; // 循环判断当前结点是不是在Condition的队列中,是的话挂起 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); }

当一个线程调用Condition.await()方法,将会以当前线程构造结点,这个结点的waitStatus赋值为Node.CONDITION,也就是-2,并将结点从尾部加入等待队列,然后尾部结点就会指向这个新增的结点,

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; }

我们依然用上面的demo来演示,此时,线程A获取锁并调用Condition.await()方法后,AQS内部的数据结构会变成这样:

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjfxf.html