Java中的线程协作之Condition
一、Condition接口
1、Condition接口的常用方法介绍
/**
* 已经获取到锁的线程调用该方法会进入等待状态,知道其他持有锁的线程通知(signal)等待队列中的线程或者被中断退出等待队列;
* 如果该线程已经从该方法中返回,表名线程已经获取到了Condition对象对应的锁
*/
public final void await() throws InterruptedException {...}
/**
* 还是进入等待状态的方法,只是该方法对中断不敏感:当前调用该方法的线程只有被通知(signal)才能从等待队列中退出
*/
public final void awaitUninterruptibly() {...}
/**
* 当前线程进入等待状态,被通知、中断或者超时之后被唤醒。返回值就是表示剩余的时间,即
* 如果在nanosTimeout纳秒之前被唤醒,返回值就是实际耗时;如果返回值是0或者负数,就认为是超时了
*/
public final long awaitNanos(long nanosTimeout) {...}
/**
* 调用该方法的线程会进入等待状态直到被通知、中断或者到达某个超时时间。
* 意味着没有到达指定的某个时间被通知,就会返回true;如果到达指定时间,返回false
*/
public final boolean awaitUntil(Date deadline){}
/**
* 当前持有Condition对象对应锁的线程,调用该方法之后会唤醒一个等待在Condition上的线程
*/
public final void signal() {}
/**
* 当前持有Condition对象对应锁的线程,调用该方法之后会唤醒等待在Condition上的所有线程
*/
public final void signalAll() {}
Condition的使用模板:Condition的获取必须通过Lock的newCondition方法,表示Condition对象与该锁关联,一般讲Condition对象作为成员变量,调用上面的await方法之后当前线程才会释放锁并在等待队列中进行等待;当其他的线程(在没有中断的情况下)调用该condition对象的signal方法的时候就会通知等待队列中的等待线程从await方法返回(返回之前已经获取锁)。
Lock lock = new ReentrantLock();
Condition con = lock.newCondition();
public void conWait() {
lock.lock();
try {
con.await();
} catch(InterruptedException e) {
...
}finally {
lock.unlock();
}
} public void conSignal() {
lock.lock();
try {
con.signal();
} catch(InterruptedException e) {
...
}finally {
lock.unlock();
}
}
2、Condition的实现分析
a)源码流程分析
我们通过跟踪源码可以看出来,首先创建锁对象(new ReentrantLock()),然后根据锁对象关联响应的Condition对象,然后通过Condition对象中维护的等待队列实现等待(await)通知(signal)机制。
public Condition newCondition() { //ReentrantLock类中的方法
return sync.newCondition();
}
//ConditionObject类实现Condition接口,除此室外ConditionObject也是AQS的一个内部类,Condition的操作需要与锁关联起来
final ConditionObject newCondition() {
return new ConditionObject();
}
//AQS的内部类ConditionObject,其中维护了一个等待队列,通过该队列实现等待通知机制
public class ConditionObject{
/**
* 返回等待队列中的线程集合
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
protected final Collection<Thread> getWaitingThreads() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION) {
Thread t = w.thread;
if (t != null)
list.add(t);
}
}
return list;
}
}
b)具体实现
上面说到了Condition是通过等待队列来实现等待通知功能的,那么就分析等待队列和等待通知机制的实现
①等待队列实现
等待队列是一个FIFO的队列,其中每个结点都包含一个处于Condition对象上等待的线程引用(当一个获取到锁的线程调用await方法,就会释放锁资源,被包装成一个Node然后添加到等待队列中进入等待状态;这里面的Node结点还是和AQS中的实现机理一样,Node是AQS中的静态内部类)。
ConditionObject类中有下面两个属性,分别代表一个Condition对应的等待队列的首节点和尾结点。当前线程调用await方法之后就会被构造成一个Node结点然后加入到等待队列的尾部。
/** Condition等待队列头结点 */
private transient Node firstWaiter;
/** Condition等待队列尾结点 */
private transient Node lastWaiter;
下面是等待队列的基本结构,Condition对象中有首尾结点的引用。新增加的结点需要将原有的尾结点的下一节点指向它,然后更新lastWaiter即可。
上面的情况是一个Condition对象对应一个等待队列和一个同步队列(上面新添加的Node3就是从同步队列中移除然后添加过来的),在同步器组件实现中,会拥有一个同步队列和多个等待队列。
②等待操作的实现
持有锁的线程调Condition的await方法之后会释放锁,然后进入等待状态。既然是持有锁的线程,那么该线程应该位于同步队列的首节点位置,其调用await方法之后就会从同步队列首节点移到等待队列的尾结点等待。具体将其移到等待队列是addConditionWaiter方法实现。下面是await方法和addConditionWaiter方法的实现分析。
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)) {
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);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
/**
* waitStatus值表示线程正在等待条件(原本结点在等待队列中,结点线程等待在Condition上,当其他线程对
* Condition调用了signal()方法之后)该结点会从等待队列中转移到同步队列中,进行同步状态的获取
* static final int CONDITION = -2;
*/
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); //构造成Condition的等待队列中的对应的结点
//增加的结点需要将原有的尾结点的下一节点指向它,然后更新lastWaiter
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
③通知操作的实现
通知操作的实现机制就是将当前等待队列中的首节点中的线程唤醒,将其加入同步队列中。
public final void signal() {
if (!isHeldExclusively()) //检查当前线程是否获取锁
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
唤醒线程使其进入同步队列之后,我们再来看await方法中那些没有执行的代码。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); //将当前线程加入等待队列
int savedState = fullyRelease(node); //释放当前线程持有的锁
int interruptMode = 0;
//根据下面的源码可以看出,当前线程如果掉用await方法之后会进入等待队列,那么在退出等待队列之前会一直执行这个循环
while (!isOnSyncQueue(node)) {
LockSupport.park(this); //唤醒节点中的线程
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//通过acquireQueued源码可以发现,获取锁的流程和ReentrantLock这种独占式获取同步状态的流程基本一致
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null) //判断当前队列是否在等待队列中
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
//竞争锁资源的同步队列
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); //得到当前结点的前驱结点
if (p == head && tryAcquire(arg)) { //当前结点的前驱结点为头结点,并且尝试获取锁成功
setHead(node); //将当前获取到锁的结点设置为头结点
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果获取同步状态失败,应该自旋等待继续获取并且校验自己的中断标志位信息
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
从上面的代码中我们可以看出,当调用await方法的线程在没有回到同步队列之前,都会一直在while (!isOnSyncQueue(node)){...}循环中,只有被唤醒退出等待队列进入同步队列才会从循环中退出;之后调用acquireQueued()开始自旋等待锁的获取,这个自旋的过程和前面介绍的AQS中独占式锁的获取流程一样;最后,如果线程从这个自旋的过程退出了,就代表当前线程再次获取了锁资源,最后也从await方法返回。所以,一个线程调用await方法之后,只有最终获取到锁才会从该方法返回。而对于signalAll而言就是对等待队列中的每个线程通知(signal)一次,这样就可以将等待队列中的所有线程移到同步队列中进行锁资源的获取。
二、Condition接口使用
1、Condition接口搭配ReentrantLock实现生产者消费者模式
package cn.source.condition; import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class ConditionProducerAndConsumer<E> { private LinkedList<E> list = new LinkedList<E>();
private static final int MAX_NUM = 10; //容器的最大数量
private int count = 0; //容器中实际数量 private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition(); private int getCount() {
return count;
} private void put(E e) {
lock.lock(); //首先需要获取锁
try {
//这里是判断容器是否已满,注意需要使用while:如果使用if的话可能导致所有的消费线程都处于等待状态
while(list.size() == MAX_NUM) {
System.out.println(Thread.currentThread().getName() + "正在等待中");
producer.await(); //生产者线程进入等待状态
}
//添加元素
list.add(e);
count ++;
consumer.signalAll();//将消费者线程唤醒
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
} private E get() {
E e = null;
lock.lock();
try {
while(list.size() == 0) {
System.out.println(Thread.currentThread().getName() + "正在等待");
consumer.await(); //消费者线程进入等待状态
}
e = list.removeFirst();
count --;
producer.signalAll(); //消费元素之后,将生产者线程唤醒
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
return e;
} public static void main(String[] args) {
SyncProducerAndConsumer<String> syncProducerAndConsumer = new SyncProducerAndConsumer<>();
for (int i = 0; i < 10; i++) { //开启10个线程
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) { //每个线程从容器中获取5次数据
System.out.println(syncProducerAndConsumer.get());
}
} }, "消费者线程" + i).start();;
}
//休眠2秒,所有的消费者线程都已经启动并且处于等待状态
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} for (int i = 0; i < 2; i++) { //开启两个生产者线程
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 25; j++) { //每个生产者线程想容器中添加25个数据,当容器中数据到达10个的时候生产者线程会阻塞
syncProducerAndConsumer.put("add value " + j);
}
}
}, "生产者线程"+i).start();
}
} }
2、synchronized组合wait/notify实现生产者消费者模式
package cn.source.condition; import java.util.LinkedList;
import java.util.concurrent.TimeUnit; public class SyncProducerAndConsumer<E> { private LinkedList<E> list = new LinkedList<E>();
private static final int MAX_NUM = 10; //容器的最大数量
private int count = 0; //容器中实际数量 public synchronized int getCount() {
return count;
} public synchronized void put(E e) {
while(list.size() == MAX_NUM) { //这里是判断容器是否已满,注意需要使用while:如果使用if的话可能导致所有的消费线程都处于等待状态
try {
this.wait(); //容器满了之后,生产者线程进入等待状态
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
//容器未满,生产者线程就想容器中添加数据
list.add(e);
count ++;
this.notifyAll(); //此时容器中已经存在数据,唤醒等待的消费者线程
} public synchronized E get() {
E e = null;
while(list.size() == 0) { //判断容器是否为空,如果为空就进入等待状态,这里也使用while
try {
this.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
e = list.removeFirst();
count --;
this.notifyAll();
return e;
} public static void main(String[] args) {
SyncProducerAndConsumer<String> syncProducerAndConsumer = new SyncProducerAndConsumer<>();
for (int i = 0; i < 10; i++) { //开启10个线程
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) { //每个线程从容器中获取5次数据
System.out.println(syncProducerAndConsumer.get());
}
} }, "消费者线程" + i).start();;
}
//休眠2秒,所有的消费者线程都已经启动并且处于等待状态
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} for (int i = 0; i < 2; i++) { //开启两个生产者线程
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 25; j++) { //每个生产者线程想容器中添加25个数据,当容器中数据到达10个的时候生产者线程会阻塞
syncProducerAndConsumer.put("add value " + j);
}
}
}, "生产者线程"+i).start();
}
} }
3、Object中的等待唤醒机制和Condition的等待通知机制对比
Java中的线程协作之Condition的更多相关文章
- Java中的线程--Lock和Condition实现线程同步通信
随着学习的深入,我接触了更多之前没有接触到的知识,对线程间的同步通信有了更多的认识,之前已经学习过synchronized 实现线程间同步通信,今天来学习更多的--Lock,GO!!! 一.初时Loc ...
- Java多线程之线程协作
Java多线程之线程协作 一.前言 上一节提到,如果有一个线程正在运行synchronized 方法,那么其他线程就无法再运行这个方法了.这就是简单的互斥处理. 假如我们现在想执行更加精确的控制,而不 ...
- Java中的线程到底有哪些安全策略
摘要:Java中的线程到底有哪些安全策略呢?本文就为你彻底分析下! 本文分享自华为云社区<[高并发]线程安全策略>,作者:冰 河 . 一.不可变对象 不可变对象需要满足的条件 (1)对象创 ...
- Java中的线程
http://hi.baidu.com/ochzqvztdbabcir/item/ab9758f9cfab6a5ac9f337d4 相濡以沫 Java语法总结 - 线程 一 提到线程好像是件很麻烦很复 ...
- [译]线程生命周期-理解Java中的线程状态
线程生命周期-理解Java中的线程状态 在多线程编程环境下,理解线程生命周期和线程状态非常重要. 在上一篇教程中,我们已经学习了如何创建java线程:实现Runnable接口或者成为Thread的子类 ...
- Java中的线程Thread总结
首先来看一张图,下面这张图很清晰的说明了线程的状态与Thread中的各个方法之间的关系,很经典的! 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口. 要注意的是Threa ...
- JAVA中创建线程的三种方法及比较
JAVA中创建线程的方式有三种,各有优缺点,具体如下: 一.继承Thread类来创建线程 1.创建一个任务类,继承Thread线程类,因为Thread类已经实现了Runnable接口,然后重写run( ...
- 浅谈利用同步机制解决Java中的线程安全问题
我们知道大多数程序都不会是单线程程序,单线程程序的功能非常有限,我们假设一下所有的程序都是单线程程序,那么会带来怎样的结果呢?假如淘宝是单线程程序,一直都只能一个一个用户去访问,你要在网上买东西还得等 ...
- 第9章 Java中的线程池 第10章 Exector框架
与新建线程池相比线程池的优点 线程池的分类 ThreadPoolExector参数.执行过程.存储方式 阻塞队列 拒绝策略 10.1 Exector框架简介 10.1.1 Executor框架的两级调 ...
随机推荐
- 处理 oracle 数据库导入报错“IMP-00058: 遇到 ORACLE 错误 942”
在导入数据文件的时候出现了下图错误: 经过多次百度搜索问题.得知问题错误方向: 仔细的查询了被导入数据的数据库的版本: 而 被导入的数据包 dmp 文件是从 oracle11g r2的版本导出的. 所 ...
- UiAutomator2.0 - 获取同行控件
目录 问题:UI测试时,在同一个界面出现相同的属性的控件(如图),对于这种控件的获取很是无奈.如果直接通过控件id去查找的话总是会返回界面该类型的第一个控件. 解决: 1.UiObject2 中已经给 ...
- 软件工程作业-(third)
1.选题目(1) 最大连续子数组和(最大子段和) 问题:给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],-,a[n],求该序列如a[i]+a[i+1]+-+a[j]的子段和的最大值. ...
- string对象方法
一:str.isalnum() ,str.isalpha(),str.isdigit() ,str.islower() ,str.isupper() 1.str.isalnum() This meth ...
- Centos设置防火墙与开放访问端口
一. jeuxs在启动后可能会出现启动jexus成功,但是访问失败.但是在服务器内部访问没问题. 列出所有端口 netstat -ntlp 查看已经开放的端口: firewall-cmd --list ...
- 关于vue的computed、filters、watch
filters 这个属性大家可能用的不是很多 因为一般的数组过滤我们用 es6的filter就能完成了 我想到一个场景,网上买书促销 满100减50 满两百减100 <input type=&q ...
- SQL反模式学习笔记4 建立主键规范【需要ID】
目标:建立主键规范 反模式:每个数据库中的表都需要一个伪主键Id 在表中,需要引入一个对于表的域模型无意义的新列来存储一个伪值,这一列被用作这张表的主键, 从而通过它来确定表中的一条记录,即便其他的列 ...
- springMVC 多文件上传前后台demo
只是个demo,需要数据校验,流程是通的 配置上传文件的解析器 前端代码; <%@ page language="java" contentType="text/h ...
- c++/qt的数据序列化和反序列化
序列化以及反序列化的实现 struct Body { double weight; double height; }; //结构体 struct People { int age; Body dBod ...
- HEX SDUT 3896 17年山东省赛D题
HEX SDUT 3896 17年山东省赛D题这个题是从矩形的左下角走到右上角的方案数的变形题,看来我对以前做过的题理解还不是太深,或者是忘了.对于这种题目,直接分析它的性质就完事了.从(1,1)走到 ...