Java并发(十一):Condition条件
先做总结:
1、为什么使用Condition条件?
synchronized配合Object的wait()、notify()系列方法可以实现等待/通知模式。
Lock提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活。
Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。
2、Condition条件实现原理:
(1)lock.newCondition(),new一个ConditionObject对象,ConditionObject有一个单向等待队列
(2)condition等待队列:线程已经拿到锁,但是因为不满足某个条件,被放入等待队列并释放锁,不能获取锁
AQS同步队列:线程抢锁失败,被放入AQS阻塞队列,等前面线程释放锁之后自己再获取锁
(3)await():当前线程T加入条件等待队列 释放锁 park()当前线程
signal():当前线程T节点出条件等待队列 T节点加入AQS同步队列 unpark()T线程
一、Condition实现生产者消费者问题
class BoundedBuffer {
final Lock lock = new ReentrantLock();
// condition 依赖于 lock 来产生
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
// 生产
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 队列已满,等待,直到 not full 才能继续生产
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal(); // 生产成功,队列已经 not empty 了,发个通知出去
} finally {
lock.unlock();
}
}
// 消费
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 队列为空,等待,直到队列 not empty,才能继续消费
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal(); // 被我消费掉一个,队列 not full 了,发个通知出去
return x;
} finally {
lock.unlock();
}
}
}
二、lock与condition关系
// 一个lock可以new多个条件
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
lock.lock();
Condition newCondition1 = lock.newCondition();
newCondition1.await();
newCondition1.signal();
Condition newCondition2 = lock.newCondition();
newCondition2.await();
newCondition2.signal();
lock.unlock();
} // ReentrantLock.newCondition()
public Condition newCondition() {
return sync.newCondition();
} // ReentrantLock.Sync.newCondition()
final ConditionObject newCondition() {
return new ConditionObject();
}
三、条件队列
每个Condition对象都包含着一个FIFO队列,该队列是Condition对象通知/等待功能的关键。在队列中每一个节点(使用的AQS的节点)都包含着一个线程引用,该线程就是在该Condition对象上等待的线程。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
private transient Node firstWaiter; //头节点
private transient Node lastWaiter; //尾节点
public ConditionObject() {
}
/**
* 通过addConditionWaiter()方法理解等待队列数据结构
* 将当前线程加入条件等待队列
* 1.Node就是AQS的Node
* 2.单向链表,通过nextWaiter连接
* 3.waitStatus==Node.CONDITION才能在等待队列中
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
/**
* 清除队列中不是等待状态的线程
*/
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null; // 用于保存前一个节点
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
}
四、等待await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // 当前线程new Node()加入条件队列
int savedState = fullyRelease(node); // 释放当先线程的锁
int interruptMode = 0;
/**
* 自旋:
* 1.当前节点不在同步队列(刚new的节点肯定不在),挂起当前线程,等待被唤醒
* 2.当其他线程调用同一个ConditionObject的signal方法时,会将队列里的节点放入同步队列,并unpark线程(排队唤醒)
* 3.如果该节点被唤醒,再自旋检查是否在同步队列。发现已经在队列中,就可以跳出循环,获取lock
*/
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);
}
五、唤醒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);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); // 加入同步队列
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒线程
return true;
}
await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout – 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了。
awaitUninterruptibly() :造成当前线程在接到信号之前一直处于等待状态。【注意:该方法对中断不敏感】。
awaitUntil(Date deadline) :造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。
signal():唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。
signal()All:唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。
参考资料 / 相关推荐
一行一行源码分析清楚 AbstractQueuedSynchronizer (二)
Java多线程系列--“JUC锁”06之 Condition条件
Java并发(十一):Condition条件的更多相关文章
- java 并发时使用条件变量--Condition
lock--unlock的方式在实际中使用较少,一般使用synchronized获取对象的内部锁替代,但是lock--unlock对了解synchronized有很大的帮助. 创建一个bank对象用于 ...
- Java并发——使用Condition线程间通信
线程间通信 线程之间除了同步互斥,还要考虑通信.在Java5之前我们的通信方式为:wait 和 notify.Condition的优势是支持多路等待,即可以定义多个Condition,每个condit ...
- 浅谈Java中的Condition条件队列,手摸手带你实现一个阻塞队列!
条件队列是什么?可能很多人和我一样答不出来,不过今天终于搞清楚了! 什么是条件队列 条件队列:当某个线程调用了wait方法,或者通过Condition对象调用了await相关方法,线程就会进入阻塞状态 ...
- Java ReEntrantLock 之 Condition条件(Java代码实战-002)
import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurren ...
- JAVA并发编程--Condition
Condition主要是为了在J.U.C框架中提供和Java传统的监视器风格的wait,notify和notifyAll方法类似的功能. AQS等待队列与Condition队列是两个相互独立的队列 a ...
- java并发:Condition的应用
Condition类可以使线程等待,也可以唤醒线程.Condition类的await方法和Object类的wait方法等效Condition类的signal方法和Object类的notify方法等效C ...
- Java并发:Condition接口
Condition 接口与 Lock 配合实现了等待 / 通知模式,这个和 Object 的监视器方法(wait.notify.notifyAll 等方法)一样,都是实现了等待 / 通知模式,但这两者 ...
- java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)
Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了 ...
- 【Java并发系列04】线程锁synchronized和Lock和volatile和Condition
img { border: solid 1px } 一.前言 多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制.这是Java并发编程中必须要理解的一个知识点.其实使用起来还是比 ...
随机推荐
- 设置display:inline-block产生间隙
display:inline-block产生间隙,是由于换行在内的空白符 display:inline-block在IE下仅仅是触发了layout,而它本是行布局,触发后,块元素依然还是行布局.所以需 ...
- 机器学习-kNN-寻找最好的超参数
一 .超参数和模型参数 超参数:在算法运行前需要决定的参数 模型参数:算法运行过程中学习的参数 - kNN算法没有模型参数- kNN算法中的k是典型的超参数 寻找好的超参数 领域知识 经验数值 实验搜 ...
- 使用chardet判断编码方式
1. chardet是什么 chardet是python中比较常用的一个编码方式检测库,需要注意的是它只检测并返回检测结果,并不负责对原数据做什么处理. 可以使用PIP命令安装: pip instal ...
- 深入理解C指针----学习笔记
深入理解C指针 第1章 认识指针 理解指针的关键在于理解C程序如何管理内存,指针包含的就是内存地址. 1.1 指针和内存 C程序在编译后,以三种方式使用内存: 1. 静态. ...
- 离散化&&逆序数对
题目:http://www.fjutacm.com/Problem.jsp?pid=3087 #include<stdio.h> #include<string.h> #inc ...
- Struts访问servletAPI方式
1.原理
- [002] delete_duplication_of_linked_list
[Description] Given a unsort linked list, delete all the duplication from them, no temporary space p ...
- Linux kernel kfifo分析【转】
转自:https://zohead.com/archives/linux-kernel-kfifo/ 本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/li ...
- java之正则表达式、日期操作
正则表达式和日期操作 正则表达式简介 正则表达式就是使用一系列预定义的特殊字符来描述一个字符串的格式规则,然后使用该格式规则匹配某个字符串是否符合格式要求. 作用:比如注册邮箱,邮箱有用户名和密码,一 ...
- C# String.Format用法和格式说明
1.格式化货币(跟系统的环境有关,中文系统默认格式化人民币,英文系统格式化美元) string.Format("{0:C}",0.2) 结果为:¥0.20 (英文操作系统结果:$0 ...