AQS之独占模式和共享模式

由于ReentrantLock是一个独占锁,独占锁的知识可以参考AQS之理论知识(一)和AQS之公平锁和非公平锁(二) 两篇文章,本文重点讲解共享模式。并且本文共享模式的讲解以CountDownLatch为主。

一、概念

​ AQS提供了两种工作模式:独占(exclusive)模式和共享(shared)模式。它的所有子类中,要么实现并使用了它独占功能的 API,要么使用了共享功能的API,而不会同时使用两套 API,即便是它最有名的子类 ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别实现的两套 API 来实现的。

​ 独占模式即当锁被某个线程成功获取时,其他线程无法获取到该锁,共享模式即当锁被某个线程成功获取时,其他线程仍然可能获取到该锁。

1.1 独占模式

​ 同一时间只有一个线程能拿到锁执行,锁的状态只有0和1两种情况。

1.2 共享模式

​ 同一时间有多个线程可以拿到锁协同工作,锁的状态大于或等于0。

1.3 API对比

独占模式 共享模式
tryAcquire(int arg) tryAcquireShared(int arg)
acquire(int arg) acquireShared(int arg)
acquireQueued(final Node node, int arg) doAcquireShared(int arg)
tryRelease(int arg) tryReleaseShared(int arg)
release(int arg) releaseShared(int arg)

二、AQS重要方法

2.1 acquireShared

​ 该方法的调用链:tryAcquireShared->doAcquireShared。其中tryAcquireShared由子类根据不同的业务需求实现,在Semaphore重要方法具体分析。doAcquireShared在AQS中已经实现,直接调用即可。

/*** Acquires in shared uninterruptible mode.* @param arg the acquire argument*/
private void doAcquireShared(int arg) {/*设置节点为共享模式,并插入队尾*/final Node node = addWaiter(Node.SHARED);boolean failed = true;try {boolean interrupted = false;for (;;) {/*获取前驱节点,如果为头节点的话,则尝试获取锁*/final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);/*r>=0代表获取锁成功,线程被唤醒*/if (r >= 0) {/*设置新的头节点并且唤醒同步队列中的线程*/setHeadAndPropagate(node, r);p.next = null; // help GC/*检测线程是否被中断过,如果中断过则再次调用中断逻辑*/if (interrupted)selfInterrupt();failed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

​ doAcquireShared方法的主要路径是addWaiter->tryAcquireShared->setHead()->doReleaseShared(),对应下面的业务大概:

2.1.1 线程信息包装成节点并添加到队尾;

2.1.2 竞争锁资源

2.1.3 获取节点头结点并 放在队列第一个

2.1.4 唤醒当前节点后的节点,如果队列头部被修改,循环唤醒下一个。

2.2 releaseShared

该方法的调用链:tryReleaseShared->doReleaseShared。其中tryReleaseShared由子类根据不同的业务需求实现,在Semaphore重要方法具体分析。doReleaseShared在AQS中已经实现,直接调用即可。

private void doReleaseShared() {/** Ensure that a release propagates, even if there are other* in-progress acquires/releases.  This proceeds in the usual* way of trying to unparkSuccessor of head if it needs* signal. But if it does not, status is set to PROPAGATE to* ensure that upon release, propagation continues.* Additionally, we must loop in case a new node is added* while we are doing this. Also, unlike other uses of* unparkSuccessor, we need to know if CAS to reset status* fails, if so rechecking.*/// 无限循环for (;;) {// 保存头结点Node h = head;if (h != null && h != tail) { // 头结点不为空并且头结点不为尾结点// 获取头结点的等待状态int ws = h.waitStatus; if (ws == Node.SIGNAL) { // 状态为SIGNALif (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 不成功就继续continue;            // loop to recheck cases// 释放后继结点unparkSuccessor(h);}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) // 状态为0并且不成功,继续continue;                // loop on failed CAS}if (h == head) // 若头结点改变,继续循环  break;}}

​ releaseShared实现了加速唤醒,主要靠h == head这段逻辑实现,例举一种doReleaseSharedh == head不成立的场景,用户层面的线程a释放锁之后,位于队首的线程t1被唤醒,t1调用setHeadAndPropagate方法设置头节点为t1,但还未调用doReleaseShared中的unparkSuccessor方法,这时用户层面的线程b释放锁,唤醒位于队首的线程t2,t2调用setHeadAndPropagate设置新的头节点为t2,这个时候t1继续执行,最后发现队首元素已经变化,继续for循环调用unparkSuccessor方法唤醒队首元素。

doReleaseSharedh == head不成立时进入for循环持续唤醒同步队列中线程的逻辑,主要是一种加速唤醒的优化逻辑,当头节点发生变化时,说明此时有不止一个线程释放锁,而在共享模式下,锁是能够被不止一个线程所持有的,因此应该趋向于唤醒更多同步队列中的线程来获取锁。

三、CountDownLatch重要方法

3.1 概念

​ CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。

​ CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

3.2 属性

3.2.1 Sync类

​ 帮助类,同时也是一个抽象类,继承了AQS,同时又根据需要添加了一些方法,供CountDownLatch调用。

3.2.1.1 Sync() 构造方法
//初始化数值用于计数
Sync(int count) {setState(count);
}
3.2.1.2 tryAcquireShared
//如果state值等于,说明锁有效,否则锁无效
protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;
}
3.2.1.3 tryReleaseShared
 //调用一次,减一次初始值protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}
}
3.2.1.4 getCount

​ 获得锁的数值。

3.2.2 CountDownLatch()构造方法

该构造函数可以构造一个用给定计数初始化的CountDownLatch,并且构造函数内完成了sync的初始化,并设置了状态数。

    public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);}

3.2.3 countDown()

作用:此函数将递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

    public void countDown() {sync.releaseShared(1);}public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}

由代码可见, countDown()主要的流程是tryReleaseShared->doReleaseShared.其中tryReleaseShared方法上文已经简单分析,tryReleaseShared方法每次调用都会讲state值减一,只有减到值为0时,开始执行doReleaseShared(),释放该线程;

3.2.4 await()

此函数将会使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。

    public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

由代码可见,主要的方法调用链是tryAcquireShared->doAcquireSharedInterruptibly。doAcquireSharedInterruptibly和doAcquireShared方法体类似,本文不在讲述,重点讲述下逻辑。

tryAcquireShared小于0时,说明state还未被消费完,线程在代码边界处停留继续消费。

注意:CountDownLatch没有使用队列,仅用了自旋。

四、总结

4.1 区别

4.1.1 节点对象

​ 在Node类中通过nextWaiter来标识共享模式(SHARED)与独占模式(EXCLUSIVE)下的节点。

​ 共享模式的节点对象是

static final Node SHARED = new Node();
Node node = new Node(Thread.currentThread(), mode);

​ 独占模式的节点对象是

 static final Node EXCLUSIVE = null;Node node = new Node(Thread.currentThread(), mode);

4.1.2 线程唤醒的时机

​ 共享模式下,头节点获取共享锁后可以立即唤醒后继节点,而不用等待获取共享锁后释放再唤醒,唤醒后继线程有2处,一处是获取到共享锁后可以立即唤醒后续的线程,但是后续线程必须是共享模式的线程;第二处是在释放锁后唤醒后继线程,这边我认为释放锁后唤醒的后继线程可以包含独占模式,但是前提是所有的独占模式前面所有的共享模式锁都已经释放。

AQS之独占模式和共享模式相关推荐

  1. 【网摘】Oracle Dedicated server 和 Shared server(专用模式 和 共享模式) 说明

    一.  官网说明 在DBCA 建库的时候,有提示让我们选择连接类型,这里有两种类型:专用服务器模式和共享服务器模式.默认使用专用模式.如下图: Oracle 官方文档对这两种文档的说明如下: Abou ...

  2. 判断release模式_AbstractQueuedSynchronizer共享模式与基于Condition的等待/通知

    共享模式acquire实现流程 上文我们讲解了AbstractQueuedSynchronizer独占模式的acquire实现流程,本文趁热打铁继续看一下AbstractQueuedSynchroni ...

  3. ORACLE专有模式与共享模式

    专有模式:当一个用户请求连接到ORACLE的时候,ORACLE会专门的为这个user process 分配一个server process. 共享模式:一个server process可以服务多个us ...

  4. 对接斑马打印机 usb模式+打印机共享模式

    对接斑马打印机 usb形式 Zebra.Sdk.Printer请使用低版本:2.14.1989 using System; using System.Collections.Generic; usin ...

  5. Java并发指南9:AQS共享模式与并发工具类的实现

    一行一行源码分析清楚 AbstractQueuedSynchronizer (三) 转自:https://javadoop.com/post/AbstractQueuedSynchronizer-3 ...

  6. 【Android 高性能音频】AAudio 音频流 音频设备 相关配置 ( 音频设备ID | 音频流方向 | 音频设备共享模式 )

    文章目录 I . AAudio 音频流创建流程 II . AAudio 音频流构建器 设置音频设备 ID AAudioStreamBuilder_setDeviceId III . AAudio 音频 ...

  7. 深入剖析基于并发AQS的(独占锁)重入锁(ReetrantLock)及其Condition实现原理

    [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/75043422 出自[zejian ...

  8. AbstractQueuedSynchronizer 原理分析 - 独占/共享模式

    1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ...

  9. 【Android 高性能音频】AAudio 音频库 简介 ( AAudio 音频库简介 | 音频流 | 音频设备 | 共享模式 | 数据模式 )

    文章目录 I . AAudio 音频库 简介 II . AAudio 音频流 三要素 ( 设备 | 共享模式 | 数据格式 ) III . AAudio 音频设备 IV . AAudio 音频设备获取 ...

最新文章

  1. java cmd javac java
  2. 从零开始发布前端代码到服务器上_无服务器计算:让每行代码都能住上“经济适用房”...
  3. 安装VMware并新建虚拟机
  4. 【CVPR2020论文解读】300米远程深度估计:港科大重磅开源自动驾驶深度感知新技术,远超现有雷达...
  5. CSLA.Net 3.0.5 版本 教学程序,代码附教学注释
  6. 【51单片机快速入门指南】4.3.4: MPU6050使用Madgwick AHRS算法实现六轴姿态融合获取四元数、欧拉角
  7. docker 6 docker运行的底层原理
  8. python 3d绘图立方体_python绘制3D立方体
  9. silverlight计时器
  10. 用python画漂亮图-用Python画一些漂亮图形--Quora代码赏析
  11. HDU - 4607 Park Visit (树的直径)
  12. 铁路计算机工程师论文,工程技术类有关论文格式模板,关于铁路工程师职文2016年相关论文范本...
  13. VBA 字典使用小结:关键字循环
  14. 树莓派进阶之路 (031) -字符问题(1) - GBK汉字编码表(转)
  15. JS Array转JSON
  16. ios描述文件的申请
  17. 8.HTML标签-表格标签table
  18. 可编程并行通信接口8255A
  19. android动画 行星,AndroidAnimation
  20. android字体中间横线,Android TextView(EditView)文字底部或者中间 加横线

热门文章

  1. Word中的空白页,怎么也删不掉?如何操作?
  2. 12月2日-3日 | 数字化安全技术大会暨Ansys medini analyze 2021用户大会
  3. VB.net判断字符串是否以数字开头
  4. 国内六种车牌颜色代表的意义
  5. Phoenix 技术分享
  6. 内核中line discipline的注册流程以及BT hciattach进程的启动
  7. shared-disk与shared-nothing区别
  8. case when 和 coalesce
  9. MFashion Python 面试
  10. “坠入情网”不叫爱,叫寂寞