Java多线程进阶(九)—— J.U.C之locks框架:AQS共享功能剖析(4)

18cc7f57e258d2abf4607d3e1cd26fc8.jpg

本文首发于一世流云的专栏:https://segmentfault.com/blog...

一、本章概述

AQS系列的前三个章节,我们通过ReentrantLock的示例,分析了AQS的独占功能。
本章将以CountDownLatch为例,分析AQS的共享功能。CountDownLatch,是J.U.C中的一个同步器类,可作为倒数计数器使用,关于CountDownLatch的使用和说明,读者可以参考:
Java多线程进阶(十八)—— J.U.C之synchronizer框架:CountDownLatch

CountDownLatch示例
假设现在有3个线程,ThreadA、ThreadB、mainThread,CountDownLatch初始计数为1:
CountDownLatch switcher = new CountDownLatch(1);

线程的调用时序如下:

//ThreadA调用await()方法等待

//ThreadB调用await()方法等待

//主线程main调用countDown()放行

二、AQS共享功能的原理

1. 创建CountDownLatch

CountDownLatch的创建没什么特殊,调用唯一的构造器,传入一个初始计数值,内部实例化一个AQS子类:

CountDownLatch switcher = new CountDownLatch(1);

clipboard.png

可以看到,初始计数值count其实就是同步状态值,在CountDownLatch中,同步状态State表示CountDownLatch的计数器的初始大小。

2. ThreadA调用await()方法等待

CountDownLatch的await方法是响应中断的,该方法其实是调用了AQS的acquireSharedInterruptibly方法:
clipboard.png

注意tryAcquireShared方法,该方法尝试获取锁,由AQS子类实现,其返回值的含义如下:

State 资源的定义
小于0 表示获取失败
0 表示获取成功
大于0 表示获取成功,且后继争用线程可能成功

CountDownLatch中的tryAcquireShared实现相当简单,当State值为0时,永远返回成功:
clipboard.png

我们之前说了在CountDownLatch中,同步状态State表示CountDownLatch的计数器的初始值,当State==0时,表示无锁状态,且一旦State变为0,就永远处于无锁状态了,此时所有线程在await上等待的线程都可以继续执行。
而在ReentrantLock中,State==0时,虽然也表示无锁状态,但是只有一个线程可以重置State的值。这就是共享锁的含义。

好了,继续向下执行,ThreadA尝试获取锁失败后,会调用doAcquireSharedInterruptibly
clipboard.png

首先通过addWaiter方法,将ThreadA包装成共享结点,插入等待队列,插入完成后队列结构如下:
clipboard.png

然后会进入自旋操作,先尝试获取一次锁,显然此时是获取失败的(主线程main还未调用countDown,同步状态State还是1)。
然后判断是否要进入阻塞(shouldParkAfterFailedAcquire):
clipboard.png

好了,至此,ThreadA进入阻塞态,最终队列结构如下:
clipboard.png

3. ThreadB调用await()方法等待

流程和步骤2完全相同,调用后ThreadB也被加入到等待队列中:
clipboard.png

4. 主线程main调用countDown()放行

ThreadA和ThreadB调用了await()方法后都在等待了,现在主线程main开始调用countDown()方法,该方法调用后,ThreadA和ThreadB都会被唤醒,并继续往下执行,达到类似门栓的作用。

来看下countDown方法的内部:
clipboard.png

该方法内部调用了AQS的releaseShared方法,先尝试一次释放锁,tryReleaseShared方法是一个钩子方法,由CountDownLatch实现,当同步State状态值首次变为0时,会返回true:
clipboard.png
clipboard.png

先调用compareAndSetWaitStatus将头结点的等待状态置为0,表示将唤醒后续结点(ThreadA),成功后的等待队列结构如下:
clipboard.png

然后调用unparkSuccessor唤醒后继结点(ThreadA被唤醒后会从原阻塞处继续往下执行,这个在步骤5再讲):
clipboard.png

此时,等待队列结构如下:
clipboard.png

5. ThreadA从原阻塞处继续向下执行

ThreadA被唤醒后,会从原来的阻塞处继续向下执行:
由于是一个自旋操作,ThreadA会再次尝试获取锁,由于此时State同步状态值为0(无锁状态),所以获取成功。然后调用setHeadAndPropagate方法:
clipboard.png

setHeadAndPropagate方法把ThreadA结点变为头结点,并根据传播状态判断是否要唤醒并释放后继结点:
clipboard.png

①将ThreadA变成头结点
clipboard.png

②调用doReleaseShared方法,释放并唤醒ThreadB结点
clipboard.png

clipboard.png

6. ThreadB从原阻塞处继续向下执行

ThreadB被唤醒后,从原阻塞处继续向下执行,这个过程和步骤5(ThreadA唤醒后继续执行)完全一样。

setHeadAndPropagate方法把ThreadB结点变为头结点,并根据传播状态判断是否要唤醒并释放后继结点:
clipboard.png

①将ThreadB变成头结点
clipboard.png

②调用doReleaseShared方法,释放并唤醒后继结点(此时没有后继结点了,则直接break):
clipboard.png

最终队列状态如下:
clipboard.png

三、总结

AQS的共享功能,通过钩子方法tryAcquireShared暴露,与独占功能最主要的区别就是:

共享功能的结点,一旦被唤醒,会向队列后部传播(Propagate)状态,以实现共享结点的连续唤醒。这也是共享的含义,当锁被释放时,所有持有该锁的共享线程都会被唤醒,并从等待队列移除。


透彻理解Java并发编程
Java并发编程是整个Java开发体系中最难以理解但也是最重要的知识点,也是各类开源分布式框架中各个并发...

SegmentFault上的文章不再更新,系列专栏文章已在个站免费分享。

1.3k 声望
1.6k 粉丝
0 条评论
推荐阅读
分布式系统从理论到实战系列
《分布式系统从理论到实战系列》是我写的第三个专栏,也将会是涵盖面最广、知识点最复杂的一个专栏。我一直在思考怎样才能将分布式相关的知识系统化的阐述出来,网络上有很多关于分布式的文章、视频、书籍,但要...

Ressmix15阅读 6k评论 14

Java8的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft32阅读 27.5k评论 1

一文彻底搞懂加密、数字签名和数字证书!
微信搜索🔍「编程指北」,关注这个写干货的程序员,回复「资源」,即可获取后台开发学习路线和书籍来源:个人CS学习网站:[链接]前言这本是 2020 年一个平平无奇的周末,小北在家里刷着 B 站,看着喜欢的 up 主视...

编程指北71阅读 33.7k评论 20

Java11的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft28阅读 19.4k评论 3

Java5的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft13阅读 21.8k

Java9的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft20阅读 15.4k

Java13的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft17阅读 11.2k

SegmentFault上的文章不再更新,系列专栏文章已在个站免费分享。

1.3k 声望
1.6k 粉丝
宣传栏