《Java并发编程艺术》读书笔记

Condition介绍

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object中),主要包括wait()wait(long timeout)notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。Object上的监视器只有一个等待队列,但是Condition上可以有多个等待队列,每个Lock.newCondition都可以产生一个Condition对象,每个Condition对象关联到一个新的等待队列上。并且Condition支持响应中断,但是Object上的监视器不支持。

Condition接口示例

Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。

一般都会将Condition对象作为成员变量。当调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且在返回前已经获取了锁。

public interface Condition {

  	 // 当前线程进入等待状态直到被通知(signal)或被中断
void await() throws InterruptedException;
// 不响应中断等待,直到被通知(signal)
void awaitUninterruptibly();
// 等待指定时长直到被通知或中断或超时。
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 等待指定时长直到被通知或中断或超时。
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定事件就被通知,方法返回true,否则false。
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁
void signal();
// 唤醒所有等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁
void signalAll();
}

使用示例:

public static void main(String[] args) throws Exception{

    AtomicInteger count = new AtomicInteger(0) ;

    new Thread(()->{
reentrantLock.lock();
try {
for(;count.intValue() < 10; count.incrementAndGet()) {
Thread.sleep(1000L);
}
System.out.println(Thread.currentThread().getName() + "当前count=" +count.intValue() + "已达目标 准备发出信号量");
condition.signal();
}catch (Exception e) {
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
}).start(); if(count.intValue() <= 10) { reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "当前count=" + count.intValue() + "。准备await");
condition.await(); reentrantLock.unlock();
System.out.println("当前count=" + count.intValue());
}
}

Condition实现原理分析

ConditionObject是同步器AbstractQueuedSynchronizer的内部类,因为Condition的操作需要获取相关联的锁,所以作为同步器的内部类也较为合理。每个Condition对象都包含着一个队列(以下称为等待队列),该队列是Condition对象实现等待/通知功能的关键。

下面将分析Condition的实现,主要包括:等待队列、等待和通知,下面提到的Condition如果不加说明均指的是ConditionObject。

等待队列

等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。事实上,节点的定义复用了同步器中节点的定义,也就是说,同步队列和等待队列中节点类型都是同步器的静态内部类AbstractQueuedSynchronizer.Node

一个Condition包含一个等待队列,Condition拥有首节点(firstWaiter)和尾节点(lastWaiter)。当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列,等待队列的基本结构下图所示:

如图所示,Condition拥有首尾节点的引用,而新增节点只需要将原有的尾节点nextWaiter指向它,并且更新尾节点即可。上述节点引用更新的过程并没有使用CAS保证,原因在于调用await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的。

在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列,其对应关系如下图所示。

如图所示,Condition的实现是同步器的内部类,因此每个Condition实例都能够访问同步器提供的方法,相当于每个Condition都拥有所属同步器的引用。

public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException(); // 将当前线程构造成一个node,加入到wait队列中。
Node node = addConditionWaiter();
//最终调用AQS的release释放同步锁
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) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

从上面的代码流程,我们知道主流程应该是:当一个线程调用await()后,首先将当前线程构造成一个node节点,加入到当前ConditionObject实例对象的同步队列尾部,释放当前线程占用的锁,然后阻塞等待另外线程unpark唤醒当前线程,当前线程唤醒后 尝试获取同步状态,获取成功返回,继续往下走。

通知

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。

Condition的signal()方法具体实现如下:

public final void signal() {
//判断当前线程是否是锁的拥有者
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
//唤醒等待时间最长的节点(首节点)
doSignal(first);
} private void doSignal(Node first) {
do {
// 当前首节点出队列
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
} // 将condition的queue中节点移到同步队列中
// Transfers a node from a condition queue onto sync queue.
final boolean transferForSignal(Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 将当前node加入到同步队列中,等待获取锁
Node p = enq(node);
int ws = p.waitStatus;
//告诉前驱节点,在释放状态是通知自己。
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}

调用该方法的前置条件是当前线程必须获取了锁,可以看到signal()方法进行了isHeldExclusively()检查,也就是当前线程必须是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。

当前线程加入等待队列的流程图:

节点从等待队列移动到同步队列:

通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列。当节点移动到同步队列后,当前线程再使用LockSupport唤醒该节点的线程。

被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue(Node node)方法返回true,节点已经在同步队列中),进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中。

成功获取同步状态(或者说锁)之后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功地获取了锁。

Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。

Condition接口的更多相关文章

  1. 6.类似Object监视器方法的Condition接口

    在<1.有关线程.并发的基本概念>中,我们利用synchronized关键字.Queue队列.以及Object监视器方法实现了生产者消费者,介绍了有关线程的一些基本概念.Object类提供 ...

  2. Java的LockSupport工具,Condition接口和ConditionObject

    在之前我们文章(关于多线程编程基础和同步器),我们就接触到了LockSupport工具和Condition接口,之前使用LockSupport工具来唤醒阻塞的线程,使用Condition接口来实现线程 ...

  3. 并发之lock的condition接口

    13.死磕Java并发-----J.U.C之Condition 12.Condition使用总结 11.Java并发编程系列之十七:Condition接口 === 13.死磕Java并发-----J. ...

  4. java中的锁之Lock接口与Condition接口

    一.Lock源码. 1.是一个接口.一共有6个方法. 2.方法详细如下: (1)当前线程尝试获取锁.结果分两种情况,一是成功获取到锁,则返回:二是获取锁失败,则一直等待.不响应中断请求. (2)当前线 ...

  5. Java并发Condition接口

    java.util.concurrent.locks.Condition接口提供一个线程挂起执行的能力,直到给定的条件为真. Condition对象必须绑定到Lock,并使用newCondition( ...

  6. Condition接口及其主要实现类ConditionObject源码浅析

    1.引子 任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait().wait(long timeout).notify()以及notifyAll() ...

  7. Lock接口之Condition接口

    之前在写显示锁的是后,在显示锁的接口中,提到了new Condition这个方法,这个方法会返回一个Condition对象 简单介绍一下 Condition接口: 任意一个Java对象,都拥有一组监视 ...

  8. Java 并发编程之 Condition 接口

    本文部分摘自<Java 并发编程的艺术> 概述 任意一个 Java 对象,都拥有一个监视器方法,主要包括 wait().wait(long timeout).notify() 以及 not ...

  9. 精尽Spring Boot源码分析 - Condition 接口的扩展

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

随机推荐

  1. nginx服务器去掉url中的index.php 和 配置path_info

    隐藏index.php server { listen 80; server_name yourdomain.com; root /home/yourdomain/www/; index index. ...

  2. 前端基础----html初识、常用标签

    一.HTML初识 web服务本质 import socket def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ...

  3. CollectionUtils.isEqualCollection的用法

    在使用Java的集合时,有些时候会需要比较两个集合是否相等,自己写方法其实也简单,但是既然有了好的实现,就不要自己造轮子了,只要了解这个轮子是什么原理就好了. public static boolea ...

  4. 0-如何正确使用 Django的User Model

    本篇主要讨论一下User Model的使用技巧. 注意, 由于Django 1.5之后user model带来了很大的变化, 本篇内容只针对django 1.5之后的版本. 1. 确定 User Mo ...

  5. 跟我一起写Makefile(三)

    书写规则———— 规则包含两个部分,一个是依赖关系,一个是生成目标的方法. 在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来 ...

  6. bzoj 1367: [Baltic2004]sequence

    1367: [Baltic2004]sequence Time Limit: 20 Sec  Memory Limit: 64 MB Description Input Output 一个整数R Sa ...

  7. C++设计实现算法时易犯错误

    1.未初始化 指针未初始化为NULL,造成判断条件出错 数值未初始化为0,造成数组超界,内存泄漏 2. long long输入时 lld or I64d 写成d一定出错 3.gcc编译报错:程序中有游 ...

  8. PHP扩展开发--01.编写一个helloWorld扩展

    为什么要用C扩展 C是静态编译的,执行效率比PHP代码高很多.同样的运算代码,使用C来开发,性能会比PHP要提升数百倍. 另外C扩展是在进程启动时加载的,PHP代码只能操作Request生命周期的数据 ...

  9. k8s+docker学习连接汇总

    http://guide.daocloud.io/dcs/docker-9153982.html http://www.dczou.com/viemall/802.html https://wangl ...

  10. Spark RDD中的aggregate函数

    转载自:http://blog.csdn.net/qingyang0320/article/details/51603243 针对Spark的RDD,API中有一个aggregate函数,本人理解起来 ...