前言

本文是对之前AQS系列文章的一个小结,首先看看以下几个问题:

1、ReentrantLock和ReentrantReadWriteLock的可重入特性是如何实现的?

2、哪个变量控制着锁是否被占用?

3、多个线程竞争一个排它锁时,未抢到锁的线程是如何阻塞的?

4、读读真的可以一直共享不阻塞吗?

对于以上问题,你是否都能知道答案 ?是否都清楚其原理?如果是的话,就没必要阅读本文了,否则还请慢慢读来。

正文

1、可重入性的实现(针对排它锁)-问题1

可重入性是通过exclusiveOwnerThread和state一起实现的。在AQS的父类AbstractOwnableSynchronizer中有一个成员变量exclusiveOwnerThread来存放获取到独占锁时的线程,可重入的特性就是通过此变量实现的。如果根据state判断当前锁已被占用,那么再判断这个变量中存的是不是当前线程,如果是则获取到锁,锁计数+1,否则获取不到锁。(此处针对的是排它锁,共享锁不属于这个范畴)

2、AQS中state的值-问题2

state为volatile int类型,用于记录加锁状态和重入次数,但针对不同的实现类其记录方式又不同,下面分别进行说明:

【ReentrantLock】state用于记录锁的持有状态和重入次数,state=0表示没有线程持有锁;state=1表示有一个线程持有锁;state=N表示exclusiveOwnerThread这个线程N次重入了这个锁。

【ReentrantReadWriteLock】state用于记录读写锁的占用状态和持有线程数量(读锁)、重入次数(写锁),state的高16位记录持有读锁的线程数量,低16位记录写锁线程重入次数,如果这16位的值是0,表示没有线程占用锁,否则表示有线程持有锁。另外针对读锁,每个线程获取到的读锁次数由本地线程变量中的HoldCounter记录。

【Semaphore】:state用于计数。state=N表示还有N个信号量可以分配出去,state=0表示没有信号量了,此时所有需要acquire信号量的线程都等着;

【CountDownLatch】:state也用于计数,每次countDown都减一,减到0的时候唤醒被await阻塞的线程。

3、关于Node中waitStatus的值

waitStatus为volatile int 类型,有5种值,作用分别为:

1: CANCEL,即取消状态,这种状态的节点是无效节点,在执行时会直接略过,一般只有在特殊的异常场景中才会出现这种状态;

0:初始化状态,新建的Node节点都是这个状态;

-1:SIGNAL,即可唤醒状态,如果一个节点加锁失败需进入阻塞状态,必须先将它的前一个节点置为-1,自己才会进入park状态,在AQS中搜parkAndCheckInterrupt()可以发现,只要是这个方法出现的地方,前面一定有shouldParkAfterFailedAcquire(p, node)方法先将p的waitStatus置为-1;

-2:CONDITION,跟条件队列相关的状态,ReentrantReadWriteLock和ReentrantLock中暂未涉及;

-3:PROPAGATE,可传播状态,没看出来有什么用处。

4、关于head节点和tail节点

当第一个线程过来获取锁时,是不会初始化队列的,只是将exclusiveOwnerThread这个变量变为当前线程(独占锁的情况下),此时head和tail都是null;

第一个线程没执行完,第二个线程又来了,这时会初始化队列,new一个空Node赋值给head和tail,然后在tail(也就是head)后面拼上这个要排队的线程(详见下面的enq方法),此时形成了一个有两个节点的双向队列,head是new Node(),tail是新加入需排队的Node;然后再获取不到锁就会将head的waitStatus置为-1,自身挂起,等待unparkSuccessor(head)来唤醒。

addWaiter方法:如果tail不为空,则将当前的node节点赋值给tail,原先的tail变为新tail在链表上的前置节点。由此可知此链表的新增方式是后入式。这也解释了为什么唤醒线程时是从tail往前遍历找排在最前面符合条件的node节点。

 private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

在AQS类中的enq方法代码如下:跟addWaiter类似,只是多了一步初始化head/tail的步骤。

 1 private Node enq(final Node node) {
2 for (;;) {
3 Node t = tail;
4 if (t == null) { // Must initialize
5 if (compareAndSetHead(new Node())) // 初始化头节点,这时head指针指向的就是new Node()对象
6 tail = head; // 将tail也一起初始化了,这时会继续再走一遍for循环
7 } else {
8 node.prev = t; // 先将node的prev指针指向局部变量t;
9 if (compareAndSetTail(t, node)) { // 将tail所在内存地址的对象值换成node
10 t.next = node; // 将tail的next指针指向node节点,这时node节点就成了新的队尾了
11 return t; // 返回旧的队尾t
12 }
13 }
14 }
15 }

4、关于读读锁的有条件共享问题-问题4

ReentrantReadWriteLock中,读锁和读锁并不是永远都可以同时进行,如果当前运行的是读锁,而后面第一个排队的是写锁,那么再来新的读锁会进入链表排队阻塞而不是直接获取读锁,因为不这样设定则可能存在由于一直有读锁过来导致后面的写锁总是处于阻塞状态获取不到锁。

可参见readShouldBlock方法的非公平读锁实现-apparentlyFirstQueuedIsExclusive()方法

 final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}

5、Object的wait/notify和LockSupport的park/unpark方法-问题3

LockSupport对线程的唤醒和挂起,是基于Thread直接信号量进行的,而wait/notify是基于对象的,需要依赖于Monitor对象。

park/unpark的语义更容易理解,park是针对当前线程阻塞,unpark是针对指定线程唤醒,不像wait/notify那样违反常识。

unpark为指定线程提供一个许可,有这个许可线程就能继续执行;而park是等待一个许可。unpark可以在park之前执行,线程不会阻塞。但unpark不能重入叠加,多个unpark也会被一个park直接用掉。

在底层,UNSAFE的park/unpark方法操作的是Paker对象(每个线程都有一个Parker实例),对象中维护了一个_counter变量,调用unpark方法后变量从0变为1,表示拥有继续执行的许可,执行之后恢复为0,调用park方法后变量如果不是1则线程阻塞,直到变为1再唤醒继续执行。

关于AQS暂时就写这些,后面还会针对JUC包里的其他工具类进行学习。

AQS系列(七)- 终篇:AQS总结的更多相关文章

  1. 死磕 java线程系列之终篇

    (手机横屏看源码更方便) 简介 线程系列我们基本就学完了,这一个系列我们基本都是围绕着线程池在讲,其实关于线程还有很多东西可以讲,后面有机会我们再补充进来.当然,如果你有什么好的想法,也可以公从号右下 ...

  2. AQS系列(一)- ReentrantLock的加锁

    前言 AQS即AbstractQueuedSynchronizer,是JUC包中的一个核心抽象类,JUC包中的绝大多数功能都是直接或间接通过它来实现的.本文是AQS系列的第一篇,后面会持续更新多篇,争 ...

  3. 跟我学SpringCloud | 终篇:文章汇总(持续更新)

    SpringCloud系列教程 | 终篇:文章汇总(持续更新) 我为什么这些文章?一是巩固自己的知识,二是希望有更加开放和与人分享的心态,三是接受各位大神的批评指教,有任何问题可以联系我: inwsy ...

  4. jackson学习之十(终篇):springboot整合(配置类)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. JUnit5学习之八:综合进阶(终篇)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  6. OpenFaaS实战之九:终篇,自制模板(springboot+maven+jdk8)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. disruptor笔记之八:知识点补充(终篇)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. 支持JDK19虚拟线程的web框架,之五(终篇):兴风作浪的ThreadLocal

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <支持JDK19虚拟线程的web框架>系列 ...

  9. AQS系列(二)- ReentrantLock的释放锁

    前言 在AQS系列(一)中我们一起看了ReentrantLock加锁的过程,今天我们看释放锁,看看老Lea那冷峻的思维是如何在代码中笔走龙蛇的. 正文 追踪unlock方法: public void ...

随机推荐

  1. Java基础 - 原码、反码、补码

    目录 机器数 真值 原码 反码 补码 为什么使用原码. 反码. 补码 机器数 所有数字在计算机底层都是以二进制形式存在的.它的表现形式叫做机器数,这个数有正负之分,最高位为符号位.0 表示正数, 1 ...

  2. MySQL数据备份与恢复(二) -- xtrabackup工具

    上一篇介绍了逻辑备份工具mysqldump,本文将通过应用更为普遍的物理备份工具xtrabackup来演示数据备份及恢复的第二篇内容. 1.  xtrabackup 工具的安装 1.1  安装依赖包 ...

  3. css第二波

    目录 css第二波 盒子模型 浮动 三种取值 清除浮动 浮动页面布局 溢出 定位 相对定位 relative(相对定位) 绝对定位 absolute(绝对定位) 固定定位 fixed(固定) 模糊框 ...

  4. 刷oj之类的题时java Scanner读取太慢解决之道

    1.转载自一个 https://www.cpe.ku.ac.th/~jim/java-io.html 2.工具代码 class Reader { static BufferedReader reade ...

  5. [Flink] Flink的waterMark的通俗理解

    导读 Flink 为实时计算提供了三种时间,即事件时间(event time).摄入时间(ingestion time)和处理时间(processing time). 遇到的问题: 假设在一个5秒的T ...

  6. 为什么要在离线A/B测试中使用贝叶斯方法

    当涉及到假设检验时,贝叶斯方法可以取代经典的统计方法.这里将使用web分析的具体案例来演示我们的演示. 贝叶斯方法在经典统计中的重要性在此链接. https://towardsdatascience. ...

  7. 用Python简单批量处理数据

    近期碰到一个问题,两套系统之间数据同步出了差错,事后才发现的,又不能将业务流程倒退,但是这么多数据手工处理量也太大了,于是决定用Python偷个小懒. 1.首先分析数据. 两边数据库字段的值都是一样, ...

  8. html第一个程序

    2020-04-05  每日一例第27天 1.打开记事本,输入html格式语言: 2.后台代码注释: <html> <head><!--标题语句--> <ti ...

  9. 关于k12领域Lucene.net+pangu搜索引擎设计开发的一些回顾

    在中国最大的教育资源门户网站两年期间, 黄药师负责学科网搜吧的设计与开发…正好赶上了公司飞速发展的阶段.. 作为专注于k12领域内容与服务的互联网公司的一员,同时整个公司又在积极提升用户体验的氛围中, ...

  10. IDEA 新版本激活之后老是有弹窗 解决方法

    用了最新的版本的IDEA,然后用网上的方法破解到了2089年,但是打开IDEA的时候,老是出现一个弹窗,内容如下: This agent is for learning and research pu ...