6.类似Object监视器方法的Condition接口
在《1.有关线程、并发的基本概念》中,我们利用synchronized关键字、Queue队列、以及Object监视器方法实现了生产者消费者,介绍了有关线程的一些基本概念。Object类提供的wait的方法和notifyAll方法,与之对应的是Condition接口提供是await和signalAll。await(或wait)是让当前线程进入等待状态并释放锁,signalAll(或notifyAll)则是唤醒等待中的线程,使得等待中的线程有竞争锁的资格,注意只是资格,并不代表被唤醒的线程就一定会获得锁。
Condition接口的具体实现还是在AbstractQueuedSynchronizer中的内部实现的——AbstractQueuedSynchronizer$ConditionObject。ConditionObject中维护了一个“等待队列”,注意这个和AQS同步器维护的“同步队列”不同。AQS所维护的同步队列是当前等待资源(同步状态)的队列,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个节点并加入到同步队列中,同时阻塞当前线程,当同步状态被所持有的线程释放时会将同步队列中的首节点唤醒重新获取同步状态。而每个Condition维护一个等待队列,该队列的作用是一个等待signal信号的队列。这两者之间的关系是一个协同的关系,用下图的说明它们之间的协同过程:
1. AQS的同步队列如下图所示,一个头结点head指向队首,一个tail指向队尾,当线程调用lock()方法获取锁而未成功时,线程被构造成节点加入到队尾。(图中NodeA是同步队列的第一个节点,也就是获得同步状态的节点)
2.NodeA调用await()方法时,NodeA从AQS同步队列中移除,自然也就释放了锁,NodeA此时被加入到Condition的等待队列中,等待signal信号,如下图所示。
3.执行完第2步后,此时NodeB在同步队列中处于第一个节点位置,即获取到了锁,如果NodeB此时执行signal(或者signalAll)方法,NodeA将会从Condition等待队列中被移除即被唤醒,加入到同步队列中,此时NodeA仅仅是被唤醒有了在同步队列中争夺资源的资格,并不代表被唤醒后就立即获得锁,如下图所示。
4. 最后NodeB在signal执行完毕后,调用unLock方法释放锁,此时NodeA处于队首,并争夺同步状态。
以上是AQS的“同步队列”和Condition的“等待队列”之间相互协作的过程,下面从源码解析Condition的主要方法await、signal、signalAll。
public final void await() throws InterruptedException{
if (Thread.interrupted()) //线程被中断则抛出中断异常
throw new InterruptedException();
Node node = addConditionWaiter(); //将线程构造为Node节点
long savedState = fullyRelease(node); //释放锁,返回同步状态
int interruptMode = 0;
while (!isOnSyncQueue(node)) { //循环判断当前节点是否在同步队列中
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break; //检查节点在处于等待状态时是否被中断
}
//在跳出了循环,即被signal唤醒后重新加入了同步队列后,开始重新竞争锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) //acquireQueued自旋获取锁,具体分析见《2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放》中对获取同步状态的解析
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters(); //如果节点从等待状态转换为在同步队列中,并且也已经获得了锁,此时将断开此节点后面的等待节点
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
在获取锁的线程调用await时,首先会将线程构造为Node节点并释放锁,此时线程被移出同步队列加入到Condition等待队列中,接着在第7行就会while循环判断节点是否在同步队列中,当没有线程调用signal方法的时候显然线程不在同步队列,并将一直循环,直到有线程调用signal方法该线程才会被唤醒加入到同步队列中,此时才会跳出循环。
signal和signalAll方法的异同在和notify和notifyAll一样。signal只会唤醒等待队列中位于队首的节点使其具有竞争锁的资格,而signalAll则会唤醒等待队列中所有节点使所有节点都具有竞争锁的资格。
public final void signal() {
if (!isHeldExclusively()) //判断当前线程是否持有锁
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); //唤醒等待队列中的第一个节点
}
对比signalAll方法,不同点在于第6行是唤醒等待队列中的所有节点——doSignalAll(first),不再贴出代码。
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null) //transferForSignal方法将处于等待队列中的节点添加到同步队列中
}
至于doSignalAll则是循环调用transferForSignal使得所有节点都被唤醒加入到同步队列中。
当节点从等待队列中加入到同步队列中时,呼应await中的循环等待节点是否在同步队列中,await和signal的协同配合也就很清晰明了了。
6.类似Object监视器方法的Condition接口的更多相关文章
- Java 并发编程之 Condition 接口
本文部分摘自<Java 并发编程的艺术> 概述 任意一个 Java 对象,都拥有一个监视器方法,主要包括 wait().wait(long timeout).notify() 以及 not ...
- 并发之lock的condition接口
13.死磕Java并发-----J.U.C之Condition 12.Condition使用总结 11.Java并发编程系列之十七:Condition接口 === 13.死磕Java并发-----J. ...
- Java并发:Condition接口
Condition 接口与 Lock 配合实现了等待 / 通知模式,这个和 Object 的监视器方法(wait.notify.notifyAll 等方法)一样,都是实现了等待 / 通知模式,但这两者 ...
- Lock接口之Condition接口
之前在写显示锁的是后,在显示锁的接口中,提到了new Condition这个方法,这个方法会返回一个Condition对象 简单介绍一下 Condition接口: 任意一个Java对象,都拥有一组监视 ...
- C# IComparable接口、IComparer接口和CompareTo(Object x)方法、Compare()方法
在项目中经常会用到字符串比较,但是有时候对字符串的操作比较多,规则各异.比如有的地方我们需要用排序规则,有的地方需要忽略大小写,我们该如何写一个比较容易操作的比较方法呢?重新实现IComparer接口 ...
- Java的LockSupport工具,Condition接口和ConditionObject
在之前我们文章(关于多线程编程基础和同步器),我们就接触到了LockSupport工具和Condition接口,之前使用LockSupport工具来唤醒阻塞的线程,使用Condition接口来实现线程 ...
- Java根类Object的方法说明
Java中的Object类是所有类的父类,它提供了以下11个方法: public final native Class<?> getClass() public native int ha ...
- Condition接口
<Java并发编程艺术>读书笔记 Condition介绍 任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object中),主要包括wait().wait(long ...
- Condition接口及其主要实现类ConditionObject源码浅析
1.引子 任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait().wait(long timeout).notify()以及notifyAll() ...
随机推荐
- 代码编写规范说明书(c#.net与asp.net)
代码编写规范说明书(c#.net与asp.net) 目 录1 目的2 范围3 注释规范3.1 概述3.2 自建代码文件注释3.3 模块(类)注释3.4 类属性注释3.5 方法注释3.6 代码间注释4 ...
- 最新windows 0day漏洞利用
利用视屏:https://v.qq.com/iframe/player.html?vid=g0393qtgvj0&tiny=0&auto=0 使用方法 环境搭建 注意,必须安装32位p ...
- salt-stack部署
saltstack部署 环境准备 [root@server elasticsearch]# cat /etc/redhat-release CentOS release 6.6 (Final)[r ...
- 【shell编程基础3】shell编程的组合应用之二:管道及其命令
预备知识: 管道:它是一个单向的,可以把前一个的数据输出导向到下一个命令的工具,这样可以实现多个命令组合处理一套数据. 它的符号是 "|" 管道只能处理经过前面一个命令传过 ...
- linux 线程编程详解
1.线程的概念: 线程和进程有一定的相似性,通常称为轻量级的进程 同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等.但同一进程中的多个线程都有自身控制流 (它 ...
- Android 正则表达式验证手机号、姓名(包含少数民族)、身份证号
最近项目中新增的功能,需要对手机号.姓名.身份证号等一些信息进行验证,最好的方法是通过正则表达式来验证,网上查了一些资料,写了这几个工具方法. 1.验证手机号 规则:第一位只能是1,第二位为3-8中的 ...
- 用Redis存储Tomcat集群的Session
作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 前段时间,我花了不少时间来寻求一种方法,把新开发的代码推送到到生产系统中部署,生产系统要能够零宕机.对使用 ...
- CAS单点登录(SSO)服务端的部署和配置---连接MySQL进行身份认证
一.修改系统host,加入 127.0.0.1 server.test.com127.0.0.1 client1.test.com127.0.0.1 client2.test.com 二.安装grad ...
- windows系统局域网内开启远程桌面图解
需要开启防火墙 默认这里没有远程桌面 添加方法 到此远程桌面已经开好了.
- python 基础之pickle 与json 报错问题解决方案
Python 基础之pickle与json 有没有在搞pickle与json在进行数据储存的时候老是报错,这个有些让人烦恼,在之前有一篇介绍过它们的基本用法以及在使用过长中避免一些坑,但是今天在把对象 ...