详解AQS中的condition源码原理

详解,aqs,condition,源码,原理 · 浏览次数 : 132

小编点评

**condition用于显式的等待通知** condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。 **和直接用lock\\unlock去做等待通知的区别** * **lock是不会释放锁的,但是利用的condition的await则可以,且唤醒后会自动重新拿回锁。** * **Lock lock = new ReentrantLock();Condition condition = lock.newCondition();** **condition的用法** * **condition.await()方法** 用于阻塞当前线程在condition指定的等待队列中等待锁获得后重新进入同步队列中。 * **condition.signal()方法** 用于唤醒当前线程从等待队列中移除并执行等待通知的操作。 **示例** ```java Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void conditionWait() throws InterruptedException { lock.lock(); try { // if(xxxx)判断不满足条件,等待,释放锁 condition.await(); } finally { lock.unlock(); } } public void conditionSignal() throws InterruptedException { lock.lock(); try { // 做完事情了,通知condition上等待的开始抢占 condition.signal() condition.signal(); } finally { lock.unlock(); } } ```

正文

摘要:condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。

本文分享自华为云社区《AQS中的condition源码原理详细分析》,作者:breakDawn。

condition的用法

condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。

和直接用lock\unlock去做等待通知的区别在于,lock是不会释放锁的,但是利用的condition的await则可以,且唤醒后会自动重新拿回锁。

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
 lock.lock();
 try {
 // if(xxxx)判断不满足条件,等待,释放锁
 condition.await();
 } finally {
 lock.unlock();
 }
}
public void conditionSignal() throws InterruptedException {
 lock.lock();
 try {
 // 做完事情了,通知condition上等待的开始抢占
 condition.signal();
 } finally {
 lock.unlock();
 }
}

也提供了一些支持中断、支持超时的等待方法

condition 和 object.wait/notify的区别

  1. object的wait依赖sync, 只能最多有一个等待队列。 而通过newCondition可以制造多个等待队列
  2. wait不支持中断,而condition支持
  3. condition支持等待特定时间

condition原理分析

超大原理流程图

  • await(), 简单来讲就是把当前线程放入condition的等待队列中,然后调用LockSupport.park拉起线程。如果被其他线程通过signal唤醒,则放入同步队列中竞争锁,竞争成功则返回,否则继续竞争。
  • signal方法,就是拿到condition的等待队列头节点,用cas修改节点状态,改成功则唤醒线程。但有可能被别人抢先,所以需要cas操作。

代码结构部分:

​ Lock提供了newCondition接口给外部锁调用

​ 而newCondition()返回的Condition是一个接口

​ 这个接口的实现类是ConditionObject,放在AQS抽象类的内部类中

原理实现部分

等待队列

  • 每个condition都有一个属于自己的等待队列
  • 每次调用condition.await, 就插入到等待队列尾部
  • 等待队列插入封装线程的节点时不需要在尾部CAS, 因为必须先获取锁,才能调用await,因此不用CAS竞争
  • 每个Lock只有一个同步队列(用于lock()时阻塞和竞争用), 但是可能会有多个等待队列(用于condition的await)

等待过程

  1. 添加线程到condition的等待队列尾部
  2. 释放占用的锁,并唤醒同步队列的后继节点
  3. 此时肯定不在aqs的同步队列中了, 用park方法进入阻塞状态
  4. 被唤醒,唤醒时可能是通过sign()被人放入了同步队列, 也可能是被中断唤醒,因此要做checkInterruptWhileWaiting检查看是否继续, 如果同意继续,就继续睡眠,直到进入同步队列
  5. 尝试acquireQueued竞争和抢占state同步状态
  6. 退出前,顺带用unlinkCancelledWaiters清理已经不是CONDITION状态的等待队列节点
public final void await() throws InterruptedException {
 if (Thread.interrupted())
 throw new InterruptedException();
 // 添加本线程到等待队列尾部
 Node node = addConditionWaiter();
 // 释放锁,唤醒同步队列中的后继节点
 int savedState = fullyRelease(node);
 int interruptMode = 0;
 // 如果已经在同步队列中了,说明被成功sign唤醒
 while (!isOnSyncQueue(node)) {
 // 阻塞挂起
 LockSupport.park(this);
 // 确认是否需要中断时就退出
 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
 break;
 }
 // 在同步队列中,那就按同步队列的规则在队列中用CAS竞争同步状态
 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
 interruptMode = REINTERRUPT;
 // 清理已经不是CONDITION状态的等待队列节点
 if (node.nextWaiter != null) 
 unlinkCancelledWaiters();
 if (interruptMode != 0)
 reportInterruptAfterWait(interruptMode);
}

唤醒过程signal()

1.检查调用signal时,是否当前线程获取了锁,不是则抛异常

if (!isHeldExclusively())
 throw new IllegalMonitorStateException();

2.获取condition队列中的第一个等待节点

Node first = firstWaiter;
if (first != null)
 doSignal(first);

3.用CAS清除CONDITION状态

if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
 return false;

4.调用AQS的enq(firstWaitNode),将这个节点放入到同步队列的队尾(需要CAS支撑?因为可能是共享的,即使获取了锁也需要竞争)

Node p = enq(node);

5.移动入同步队列成功后(可能经历了几次CAS),再用unpark方法唤醒,那个线程就进入了上面代码中Park之后的部分了

int ws = p.waitStatus;
if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
 LockSupport.unpark(node.thread);

6.如果是signalAll方法,则等待队列中每个节点都执行一次signal方法,全部移入同步队列中并唤醒(唤醒后他们很可能还会因为抢不到资源而阻塞,但队列位置不同了,也无法再通过sign唤醒了)

do {
 Node next = first.nextWaiter;
 first.nextWaiter = null;
 transferForSignal(first);
    first = next;
} while (first != null);

 

点击关注,第一时间了解华为云新鲜技术~

与详解AQS中的condition源码原理相似的内容:

详解AQS中的condition源码原理

摘要:condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。 本文分享自华为云社区《AQS中的condition源码原理详细分析》,作者:breakDawn。 condition的用法 condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。 和

JUC中的AQS底层详细超详解

摘要:当你使用java实现一个线程同步的对象时,一定会包含一个问题:你该如何保证多个线程访问该对象时,正确地进行阻塞等待,正确地被唤醒? 本文分享自华为云社区《JUC中的AQS底层详细超详解,剖析AQS设计中所需要考虑的各种问题!》,作者: breakDawn 。 java中AQS究竟是做什么的?

详解AQS的7个同步组件

摘要:AQS的全称为Abstract Queued Synchronizer,是在J.U.C(java.util.concurrent)下子包中的类。 本文分享自华为云社区《【高并发】AQS案例详解》,作者: 冰 河。 AQS的全称为Abstract Queued Synchronizer,是在J.

【后端面经-Java】AQS详解

本文介绍了AQS的核心思想、基本架构、实现方法,并对框架中的重要源码方法进行介绍和分析

一文搞懂到底什么是 AQS

日常开发中,我们经常使用锁或者其他同步器来控制并发,那么它们的基础框架是什么呢?如何实现的同步功能呢?本文将详细用白话讲解构建锁和同步器的基础框架--AQS,并根据源码分析其原理。

详解C#委托与事件

在C#中,委托是一种引用类型的数据类型,允许我们封装方法的引用。通过使用委托,我们可以将方法作为参数传递给其他方法,或者将多个方法组合在一起,从而实现更灵活的编程模式。委托类似于函数指针,但提供了类型安全和垃圾回收等现代语言特性。 基本概念 定义委托 定义委托需要指定它所代表的方法的原型,包括返回类

详解Web应用安全系列(8)不足的日志记录和监控

在Web安全领域,不足的日志记录和监控是一个重要的安全隐患,它可能导致攻击者能够更隐蔽地进行攻击,同时增加了攻击被检测和响应的难度。以下是对Web攻击中不足的日志记录和监控漏洞的详细介绍。 一、日志记录不足的问题 日志缺失或不完整 关键操作未记录:如用户登录、敏感数据访问、系统管理员操作等关键操作未

详解Web应用安全系列(5)敏感数据泄露漏洞

在最近几年,这是最常见的,最具影响力的攻击。这个领域最常见的漏洞是不对敏感数据进行加密。在数据加密过程中,常见的问题是不安全的密钥生成和管理以及使用弱密码算法,弱协议和弱密码。特别是使用弱的哈希算法来保护密码。在服务端,检测数据传输过程中的数据弱点很容易,但检测存储数据的弱点却非常困难。 敏感数据泄

详解Web应用安全系列(4)失效的访问控制

在Web安全中,失效的访问控制(也称为权限控制失效或越权访问)是指用户在不具备相应权限的情况下访问了受限制的资源或执行了不允许的操作。这通常是由于Web应用系统未能建立合理的权限控制机制,或者权限控制机制失效所导致的。 危害 数据泄漏:攻击者可能通过越权访问获取敏感数据,如用户个人信息、财务数据、家

详解Web应用安全系列(3)失效的身份认证

大多数身份和访问管理系统的设计和实现,普遍存在身份认证失效的问题。会话管理是身份验证和访问控制的基础,并且存在于所有有状态的应用程序中。攻击者可以使用指南手册来检测失效的身份认证,但通常会关注密码转储,字典攻击,或者在类似于钓鱼或社会工程攻击之后,发现失效的身份认证。 确认用户的身份,身份验证和会话