本文部分摘自《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 接口的更多相关文章

  1. Java并发编程之CAS

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...

  2. Java并发编程之CAS第一篇-什么是CAS

    Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...

  3. Java并发编程之CAS二源码追根溯源

    Java并发编程之CAS二源码追根溯源 在上一篇文章中,我们知道了什么是CAS以及CAS的执行流程,在本篇文章中,我们将跟着源码一步一步的查看CAS最底层实现原理. 本篇是<凯哥(凯哥Java: ...

  4. Java并发编程之CAS第三篇-CAS的缺点及解决办法

    Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...

  5. Java并发编程之set集合的线程安全类你知道吗

    Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...

  6. 并发编程之 Condition 源码分析

    前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...

  7. Java并发编程之Lock

    重入锁ReentrantLock 可以代替synchronized, 但synchronized更灵活. 但是, 必须必须必须要手动释放锁. try { lock.lock(); } finally ...

  8. Java并发编程之AQS

    一.什么是AQS AQS(AbstractQueuedSynchronize:队列同步器)是用来构建锁或者其他同步组件的基础框架,很多同步类都是在它的基础上实现的,比如常用的ReentrantLock ...

  9. Java并发编程之ReentrantLock源码分析

    ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ...

随机推荐

  1. Oracle数据库故障处理方法

    1.启动数据库报错:ORA-01102:cannot mount database in EXCLUSIVE mode 给客户处理oracle故障,遇到如下报错: 以sys登录至数据库,执行shutd ...

  2. HDU 4049 Tourism Planning(状压DP)题解

    题意:m个城市,n个人,让这n个人按固定顺序走遍m个城市.每个城市有一个单人票价pi.每个人在每个城市能获得vij的价值.如果多个人在同一城市,那么会额外获得价值,给出一张n * n价值表,额外价值为 ...

  3. Next.js SSR Tutorials

    Next.js SSR Tutorials https://codesandbox.io/s/nextjs-demo-h49zt cli $ npx create-next-app ssr-demo- ...

  4. Web 开发之 HTTP/2 & SPDY & HTTP 1.1 & HTTP 对比分析详解!

    1 https://zh.wikipedia.org/wiki/HTTP/2 HTTP/2 维基百科,自由的百科全书                         HTTP/2(超文本传输协议第2版 ...

  5. CSS Grid & Flex poster PDF 海报定制

    CSS Grid & Flex poster PDF 海报定制 CSS 手工实现 导出 SVG / PNG 导出 PDF 打印,定制海报 refs https://css-tricks.com ...

  6. js double 精度损失 bugs

    js double 精度损失 bugs const arr = [ 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01 ]; // [ ...

  7. Web 全栈开发 MySQL 面试题

    Web 全栈开发 MySQL 面试题 MySQL MySQL 读写分离 读写分离原理 MySQL的主从复制和MySQL的读写分离两者有着紧密联系,首先部署主从复制,只有主从复制完了,才能在此基础上进行 ...

  8. chown -R & chmod 777 & chmod +x

    chown -R & chmod 777 & chmod +x https://linux.die.net/man/1/chown chown - change file owner ...

  9. virtual scroll list / dynamic dom list

    virtual scroll list / dynamic dom list 虚拟滚动列表 1亿条数据的处理渲染方法 时间分片,不阻塞 DOM web workers 后台进程 Array buffe ...

  10. js trigger click event & dispatchEvent & svg element

    js trigger click event & dispatchEvent & svg element but svg element not support trigger cli ...