[源码]Condition的原理,简单案例(ArrayBlockingQueue),复杂案例(LinkedBlockingQueue).

源代码解析

Re‘entrantLock lock = new ReentrantLock(fair);

Condition 
 notEmpty = lock.newCondition(); //返回内部类 AbstractQueuedSyncronizer.ConditionObject

各自维护了两个队列.一个是阻塞同步队列 syncQueue 双向队列,一个是条件等待队列.

Condition.await两个作用.1.放入同步队列 park 2.realse锁,3等待别人获取锁acquire(),并且.signal .unlock()之后调用acquiredQueue()从阻塞同步队列里复活出来.

Condition..signal 1.在父类Locker.lock()获取锁之后,从条件队列迁移到阻塞同步队列.2.等待之后的unlcok
释放锁,并唤醒next线程.

能够park .signal 完成线程加入到阻塞队列中( 因为signal必须在对应的lock()后操作. 所以从条件队列中迁移出不可能获得锁,只能加入到线程队列中.)

之前的误区: 何时await的线程被唤醒?和正在syncQueue中的线程优先级哪个高?

我理解为signal之后唤醒await线程.

正确理解: signal只是转移线程,并不是唤醒await队列的地方.真正唤醒await线程的地方在持有Locker.unlock的时候.(见LinkedBlockingQueue中的signalNotFull()方法.)
await线程被转移到syncQueue时,已经有线程在排队,那么只好放在队尾.

下面有LinkedBlockingQueue的先take,await, 然后被put.signal的时序图.

private void signalNotEmpty() {

final ReentrantLock takeLock = this.takeLock;

takeLock.lock();

try {

notEmpty.signal(); // phil:必须在lock()后面,锁已经被线程获取了.

finally {

takeLock.unlock();

}

}

ReentrantLock.unlock完成下一个线程(可能刚好是signal加入的)的unpark.

所以总结: signal后不一定是之前的那个await的线程.  获得锁执行..


 /**

* Implements interruptible condition wait.

* <ol>

* <li> If current thread is interrupted, throw InterruptedException

* <li> Save lock state returned by {@link #getState}

* <li> Invoke {@link #release} with

*      saved state as argument, throwing

*      IllegalMonitorStateException  if it fails.

* <li> Block until signalled or interrupted

* <li> Reacquire by invoking specialized version of

*      {@link #acquire} with saved state as argument.

* <li> If interrupted while blocked in step 4, throw exception

* </ol>

*/

public final void await() throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

Node node = addConditionWaiter();

int savedState = fullyRelease(node);

int interruptMode = 0;

while (!isOnSyncQueue(node)) { 
           
  //phi注:不在AbstractQueuedSyncronizer的线程阻塞队列里.就park();

condition.signal后,迁移线程到父类的阻塞线程队列,如果父类ReentrantLocker执行release()操作,唤醒队列头线程,但线程还在队列里. 所以不用死循环. 所以说: Lock的孩子的条件队列和Lock自己的阻塞队列是互斥的.

LockSupport.park(this);

if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)

break;

}

if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
// 如果是第一个,那么就获取锁,从队列中剔除并执行,如果不是就重新park()

interruptMode = REINTERRUPT;

if (node.nextWaiter != null)

unlinkCancelledWaiters();

if (interruptMode != 0)

reportInterruptAfterWait(interruptMode);

}


/**

* Removes and transfers nodes until hit non-cancelled one or

* null. Split out from signal in part to encourage compilers

* to inline the case of no waiters.

@param first (non-null) the first node on condition queue

*/

private void doSignal(Node first) {

do {

if ( (firstWaiter = first.nextWaiter) == null)
 //这一步做的操作重新设置队列头.

lastWaiter = null;

first.nextWaiter = null;                       //隔断老的队列头和原队列的连接.

while (!transferForSignal(first) && 
           //transferForSignal把线程从条件队列中转移到阻塞队列中.但没有unpark操作

(first = firstWaiter) != null);

}

/**

* Transfers a node from a condition queue onto sync queue.

* Returns true if successful.

@param node the node

@return true if successfully transferred (else the node was

* cancelled before signal).

*/

final boolean transferForSignal(Node node) {

/*

* If cannot change waitStatus, the node has been cancelled.

*/

if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))

return false;

/*

* Splice onto queue and try to set waitStatus of predecessor to

* indicate that thread is (probably) waiting. If cancelled or

* attempt to set waitStatus fails, wake up to resync (in which

* case the waitStatus can be transiently and harmlessly wrong).

*/

Node p = enq(node);

int ws = p.waitStatus;

if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))

LockSupport.unpark(node.thread);  
          //正常情况不会进到这里. 那么何时进行unpark呢? unpark是上一个线程释放锁时进行的操作. unpark阻塞队列列头的线程.

return true;

}

何时unpark()呢?

ArrayBlockingQueue. 内部只有一个Locker.

所以所有的.await都在这个locker.lock()和unlock()之间.

在.signal()和父类locker的unlocker() 两个动作先后执行之后 unpark.

LinkedBlockingQueue 中

由于 take的Condition的signal是在put()内执行的.

所以特意在.signal()的外面封装了 takeLocker.lock()和 unLock();

这两个操作(signal和unlock() )才能成为一个整体 ,实现线程从条件队列迁移到阻塞队列,然后线程的唤醒,然后从阻塞队列的剔除这个线程.

LinkedBlockingQueue的先take,await, 然后被put.signal的时序图.高清版右键下载

https://www.gliffy.com [email protected]画出来

[源码]Condition的原理,简单案例(ArrayBlockingQueue),复杂案例(LinkedBlockingQueue).

时间: 2024-10-10 04:52:06

[源码]Condition的原理,简单案例(ArrayBlockingQueue),复杂案例(LinkedBlockingQueue).的相关文章

Spring Boot 揭秘与实战 源码分析 - 工作原理剖析

文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoConfiguration4. 扩展阅读 3.1. 核心注解 3.2. 注入 Bean 结合<Spring Boot 揭秘与实战 源码分析 - 开箱即用,内藏玄机>一文,我们再来深入的理解 Spring Boot 的工作原理. 在<Spring Boot 揭秘与实战 源码分析 - 开箱即用,内藏

Tomcat7.0源码分析——请求原理分析(中)

前言 在<TOMCAT7.0源码分析--请求原理分析(上)>一文中已经介绍了关于Tomcat7.0处理请求前作的初始化和准备工作,请读者在阅读本文前确保掌握<TOMCAT7.0源码分析--请求原理分析(上)>一文中的相关知识以及HTTP协议和TCP协议的一些内容.本文重点讲解Tomcat7.0在准备好接受请求后,请求过程的原理分析. 请求处理架构 在正式开始之前,我们先来看看图1中的Tomcat请求处理架构. 图1 Tomcat请求处理架构 图1列出了Tomcat请求处理架构中的主

Tomcat源码分析——请求原理分析(下)

前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在<TOMCAT源码分析——请求原理分析(中)>一文我简单讲到了Pipeline,但并未完全展开,本文将从Pipeline开始讲解请求原理的剩余内容. 管道 在Tomcat中管道Pipeline是一个接口,定义了使得一组阀门Valve按照顺序执行的规范,Pipeline中定义的接口如下: getBas

深入理解ButterKnife源码并掌握原理(一)

前言 话说在android这座大山里,有一座庙(方块公司-square),庙里住着一个神-jake(我是这么叫的嘻嘻). 不要小看这个小jake,这个神可是为android应用开发们提供了强有力的帮助.比如流行的开源库okhttp,eventbus系列 ,retrofit,butterknife 等等都是出于他之手.小弟佩服的不要不要的-,可以说是为android的应用开发效率和耦合性提高了一个台阶啊. 其它的大神我也是佩服的不要不要的-嘻嘻 声明 这一系列的文章是对ButterKnife的源码

Tomcat7.0源码分析——请求原理分析(上)

前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程序员自己实现Jsp页面或者Servlet接受请求,后来借助Struts1.Struts2.Spring等中间件后,实际也是利用Filter或者Servlet处理请求,大家肯定要问了,这些Servlet处理的请求来自哪里?Tomcat作为Web服务器是怎样将HTTP请求交给Servlet的呢? 本文就

Tomcat源码分析——请求原理分析(中)

前言 在<TOMCAT源码分析——请求原理分析(上)>一文中已经介绍了关于Tomcat7.0处理请求前作的初始化和准备工作,请读者在阅读本文前确保掌握<TOMCAT源码分析——请求原理分析(上)>一文中的相关知识以及HTTP协议和TCP协议的一些内容.本文重点讲解Tomcat7.0在准备好接受请求后,请求过程的原理分析. 请求处理架构 在正式开始之前,我们先来看看图1中的Tomcat请求处理架构. 图1 Tomcat请求处理架构 图1列出了Tomcat请求处理架构中的主要组件,这里

Tomcat7.0源码分析——请求原理分析

Tomcat7.0源码分析--请求原理分析 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程序员自己实现Jsp页面或者Servlet接受请求,后来借助Struts1.Struts2.spring等中间件后,实际也是利用Filter或者Servlet处理请求,大家肯定要问了,这些Servlet处理的请求来自哪里?Tomcat作为Web服务器是怎样将HTT

Android 网络框架之Retrofit2使用详解及从源码中解析原理

就目前来说Retrofit2使用的已相当的广泛,那么我们先来了解下两个问题: 1 . 什么是Retrofit? Retrofit是针对于Android/Java的.基于okHttp的.一种轻量级且安全的.并使用注解方式的网络请求框架. 2 . 我们为什么要使用Retrofit,它有哪些优势? 首先,Retrofit使用注解方式,大大简化了我们的URL拼写形式,而且注解含义一目了然,简单易懂: 其次,Retrofit使用简单,结构层次分明,每一步都能清晰的表达出之所以要使用的寓意: 再者,Retr

Netty源码学习——EventLoopGroup原理:NioEventLoopGroup分析

类结构图: 不了解Executor接口原理的可以查看concurrent包中的api介绍,这里只介绍Netty中EventExecutorGroup的主要功能! 从类的结构图中可以看到EventExecutorGroup是直接继承ScheduledExecutorService这个接口的,为了说明白Group的原理这里顺便提一下ScheduledExecutorService的用途! java.util.concurrent.ScheduledExecutorService An Executo