Java 并发编程之 Condition 接口
本文部分摘自《Java 并发编程的艺术》
概述
任意一个 Java 对象,都拥有一个监视器方法,主要包括 wait()、wait(long timeout)、notify() 以及 notifyAll() 方法,这些方法与 synchronized 同步关键字配合,可以实现等待 - 通知模式。Condition 接口也提供了类似 Object 的监视器方法,与 Lock 配合可以实现等待 - 通知模式
Object 的监视器方法与 Condition 接口的对比:
| 对比项 | Object 监视器方法 | Condition |
|---|---|---|
| 前置条件 | 获取对象的监视器锁 | 调用 Lock.lock() 获取锁调用 Lock.newCondition() 获取 Condition 对象 |
| 调用方法 | 直接调用如:object.wait() | 直接调用如:condition.await() |
| 等待队列个数 | 一个 | 多个 |
| 当前线程释放锁并进入等待队列 | 支持 | 支持 |
| 当前线程释放锁并进入等待队列,在等待状态中不响应中断 | 不支持 | 支持 |
| 当前线程释放锁并进入超时等待状态 | 支持 | 支持 |
| 当前线程释放锁并进入等待状态到将来的某个时间 | 不支持 | 支持 |
| 唤醒等待队列中的一个线程 | 支持 | 支持 |
| 唤醒等待队列中的全部线程 | 支持 | 支持 |
接口示例
Condition 定义了等待 - 通知两种类型的方法,当前线程调用这些方法时,需要提前获取到 Condition 对象关联的锁。Condition 对象是由 Lock 对象(调用 Lock 对象的 newCondition() 方法)创建,换句话说,Condition 是依赖 Lock 对象的
public class ConditionUserCase {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() {
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
}
当调用 await() 方法后,当前线程会释放锁并在此等待,而其他线程调用 Condition 对象的 signal() 方法,通知当前线程后,当前线程才从 await() 方法返回,并且在返回前已经获取了锁
Condition 的部分方法以及描述:
| 方法名称 | 描 述 |
|---|---|
| void await() throws InterruptedException | 当前线程进入等待状态直到被通知(signal)或中断。 |
| void awaitUninterruptibly() | 当前线程进入等待状态直到被通知,该方法不响应中断。 |
| long awaitNanos(long nanosTimeout) throws InterruptedException | 当前线程进入等待状态直到被通知、中断或者超时,返回值表示剩余超时时间。 |
| boolean awaitUntil(Date deadline) throws InterruptedException | 当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定时间就被通知,方法返回 true,否则,表示到了指定时间,返回 false。 |
| void signal() | 唤醒一个等待在 Condition 上的线程,该线程从等待方法返回前必须获得与 Condition 相关联的锁。 |
| void signalAll() | 唤醒所有等待在 Condition 上的线程,能够从等待方法返回的线程必须获得与 Condition 相关联的锁。 |
下面通过一个有界队列的示例来深入理解 Condition 的使用方式
public class BoundedQueue<T> {
private Object[] items;
// 添加的下标,删除的下标和数据当前数量
private int addIndex, removeIndex, count;
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
public BoundedQueue(int size) {
items = new Object[size];
}
/**
* 添加一个元素,如果数组满,则添加线程进入等待状态,直到有空位
*/
public void add(T t) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await();
}
items[addIndex] = t;
if (++addIndex == items.length) {
addIndex = 0;
}
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
/**
* 由头部删除一个元素,如果数组空,则删除线程进入等待状态,直到有新元素添加
*/
@SuppressWarnings("unchecked")
public T remove() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
Object x = items[removeIndex];
if (++removeIndex == items.length) {
removeIndex = 0;
}
--count;
notFull.signal();
return (T) x;
} finally {
lock.unlock();
}
}
}
实现分析
ConditionObject 是同步器 AbstractQueuedSynchronizer 的内部类,每个 Condition 对象都包含着一个队列(等待队列),该队列是 Condition 对象实现等待 - 通知功能的关键
1. 等待队列
等待队列是一个 FIFO 队列,在队列中的每个节点都包含了一个线程引用,该线程就是在 Condition 对象上等待的线程,如果一个线程调用了 Condition.await() 方法,那么该线程就会释放锁,构造成节点并加入等待队列并进入等待状态
一个 Condition 包含一个等待队列,Condition 拥有首尾节点的引用,新增节点只需要将原有的尾节点 nextWaiter 指向它,并更新尾节点即可。节点引用更新的过程并没有使用 CAS 来保证,原因在于调用 await() 方法的线程必定是获取了锁的线程,也就是该过程是由锁来保证线程安全的

在 Object 的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的 Lock 拥有一个同步队列和多个等待队列,其对应关系如图所示:

2. 等待
调用 Condition 的 await() 方法,会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从 await() 方法返回时,当前线程一定获取了 Condition 相关联的锁
Condition 的 await() 方法如下所示:
public final void await() throws InterruptedException {
// 检测线程中断状态
if (Thread.interrupted())
throw new InterruptedException();
// 当前线程包装为 Node 并加入等待队列
Node node = addConditionWaiter();
// 释放同步状态,也就是释放锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 检测该节点是否在同步队列中,如果不在,则继续等待
while (!isOnSyncQueue(node)) {
// 挂起线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 竞争同步状态
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清理条件队列中的不是在等待条件的节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// 对等待线程中断,会抛出异常
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
3. 通知
调用 Condition 的 signal() 方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中
Condition 的 signal() 方法代码如下所示:
public final void signal() {
// 检查当前线程是否获取了锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取等待队列首节点,移动到同步队列并唤醒
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
Condition 的 signAll() 方法,相当于对等待队列中的每个结点均执行一个 signal() 方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程
Java 并发编程之 Condition 接口的更多相关文章
- Java并发编程之CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...
- Java并发编程之CAS第一篇-什么是CAS
Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...
- Java并发编程之CAS二源码追根溯源
Java并发编程之CAS二源码追根溯源 在上一篇文章中,我们知道了什么是CAS以及CAS的执行流程,在本篇文章中,我们将跟着源码一步一步的查看CAS最底层实现原理. 本篇是<凯哥(凯哥Java: ...
- Java并发编程之CAS第三篇-CAS的缺点及解决办法
Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...
- Java并发编程之set集合的线程安全类你知道吗
Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...
- 并发编程之 Condition 源码分析
前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...
- Java并发编程之Lock
重入锁ReentrantLock 可以代替synchronized, 但synchronized更灵活. 但是, 必须必须必须要手动释放锁. try { lock.lock(); } finally ...
- Java并发编程之AQS
一.什么是AQS AQS(AbstractQueuedSynchronize:队列同步器)是用来构建锁或者其他同步组件的基础框架,很多同步类都是在它的基础上实现的,比如常用的ReentrantLock ...
- Java并发编程之ReentrantLock源码分析
ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ...
随机推荐
- K8S(14)监控实战-grafana出图_alert告警
k8s监控实战-grafana出图_alert告警 目录 k8s监控实战-grafana出图_alert告警 1 使用炫酷的grafana出图 1.1 部署grafana 1.1.1 准备镜像 1.1 ...
- kubernetes进阶(五)dashboard--WEB管理
dashboard是k8s的可视化管理平台,是三种管理k8s集群方法之一 首先下载镜像上传到我们的私有仓库中:hdss7-200 # docker pull k8scn/kubernetes-dash ...
- Java 并发机制底层实现 —— volatile 原理、synchronize 锁优化机制
本书部分摘自<Java 并发编程的艺术> 概述 相信大家都很熟悉如何使用 Java 编写处理并发的代码,也知道 Java 代码在编译后变成 Class 字节码,字节码被类加载器加载到 JV ...
- python--通过ocr对数据可视化视频还原为csv,进行简单的分析
见github https://github.com/TouwaErioH/Machine-Learning/tree/master/video/video 题目描述: source https:// ...
- 51nod 1384 可重集的全排列
对于1231,121,111等有重复的数据,我们怎么做到生成全排列呢 实际上,对于打标记再释放标记的这种方法,如果一开始第一层递归访问过1那么你再访问 就会完全重复上一次1开头的情况,那么递归地考虑这 ...
- Docker下载Mysql 2059
接着我上一篇的,在我们安装好docker后我们尝试在docker里面安装mysql并连接使用 正式开始 1.启动docker 2.打开Windows PowerShell(执行下载命令) `docke ...
- 修改jupyter-notebook的python3版本
将默认的kernel修改为对应的python即可: /home/a/.virtualenvs/YOUR_VENV/bin/python -m pip install ipykernel /home/a ...
- Code Spell Checker & VSCode 单词拼写验证
Code Spell Checker & VSCode 单词拼写验证 https://marketplace.visualstudio.com/items?itemName=streetsid ...
- CURL (CommandLine Uniform Resource Locator) 简易教程!
1 http://curl.haxx.se/ http://curl.haxx.se/docs/httpscripting.html curl is an open source command li ...
- Java IO 通信 All In One
Java IO 通信 All In One Netty / WebSocket BIO 通信 BIO 即阻塞 I/O,不管是磁盘 I/O 还是网络 I/O,数据在写入 OutputStream 或者从 ...