文档章节

Condition.await, signal 与 Object.wait, notify 的区别

w
 walle-Liao
发布于 2016/01/24 00:12
字数 985
阅读 761
收藏 0

Object 类中 wait,notify 与 notifyAll 方法可以用来实现线程之间的调度,比如在阻塞队列(BlockingQueue)的实现中,如果队列为空,则所有消费者线程进行阻塞 ( wait ),如果某一个时刻队列中新添加了一个元素,则需要唤醒某个或所有阻塞状态的消费者线程( notify,notifyAll ),同理如果是队列已满,则所有生产者线程都需要阻塞,等到某个元素被消费之后又需要唤醒某个或所有正在阻塞的生产者线程

Condition 的 await,signal, singalAll 与 Object 的 wait, notify, notifyAll 都可以实现的需求,两者在使用上也是非常类似,都需要先获取某个锁之后才能调用,而不同的是 Object wait,notify 对应的是 synchronized 方式的锁,Condition await,singal 则对应的是 ReentrantLock (实现 Lock 接口的锁对象)对应的锁

来看下面具体的示例:使用 wait, notify 和 await, signal 方式分别实现一个简单的队列

public interface SimpleQueueDemo<E> {	
	void put(E e);
	
	E take();
}

基于 Object wait, notify 的实现

public class SynchronizedQueue<E> implements SimpleQueueDemo<E> {
	
	private Object[] array;
	private int index = 0;
	
	public SynchronizedQueue(int size) {
		array = new Object[size];
	}
	
	@Override
	public synchronized void put(E item) {
		while(isFull()) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		array[index++] = item;
		
		this.notifyAll();
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public synchronized E take() {
		while(isEmpty()) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		E item = (E) array[0];
		array = Arrays.copyOfRange(array, 1, array.length + 1);
		array[array.length - 1] = null;
		index--;
		
		this.notifyAll();
		return item;
	}
	
	private boolean isFull() {
		return index >= array.length;
	}
	
	private boolean isEmpty() {
		return index <= 0;
	}

}

基于 await, signal 的实现

public class ConditionQueue<E> implements SimpleQueueDemo<E> {

	private Object[] array;
	private int index = 0;
	
	private static ReentrantLock lock = new ReentrantLock();
	
	private static Condition notEmpty = lock.newCondition();
	private static Condition notFull = lock.newCondition();
	
	public ConditionQueue(int size) {
		this.array = new Object[size];
	}
	
	@Override
	public void put(E item) {
		lock.lock();
		try {
			while(isFull()) {
				notFull.await();
			}
			
			array[index++] = item;
			
			notEmpty.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public E take() {
		lock.lock();
		try {
			while(isEmpty()) {
				notEmpty.await();
			}
			
			E item = (E) array[0];
			array = Arrays.copyOfRange(array, 1, array.length + 1);
			array[array.length - 1] = null;
			index--;
			
			notFull.signal();
			return item;
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return null;
	}
	
	private boolean isFull() {
		return index >= array.length;
	}
	
	private boolean isEmpty() {
		return index <= 0;
	}

}

两者在使用形式和实现的功能上都非常的类似,但这里面有一个最大的问题就是 synchronized 方式对应的 wait, notify 不能有多个谓词条件,Lock 对应的 Condition await, signal 则可以有多个谓词条件

private static ReentrantLock lock = new ReentrantLock();
	
private static Condition notEmpty = lock.newCondition();
private static Condition notFull = lock.newCondition();

没有多个谓词条件带来的问题在于

例如队列已满,所有的生产者现场阻塞,某个时刻消费者消费了一个元素,则需要唤醒某个生产者线程,而通过 Object notify 方式唤醒的线程不能确保一定就是一个生产者线程,因为 notify 是随机唤醒某一个正在该 synchronized 对应的锁上面通过 wait 方式阻塞的线程,如果这时正好还有消费者线程也在阻塞中,则很可能唤醒的是一个消费者线程;signalAll 更是会唤醒所有在对应锁上通过 wait 方式阻塞的线程,而不管是生产者还是消费者线程。

与之不同的 Condition await, signal 方式则可以对应多个谓词条件(notEmpty, notFull),可以很方便的实现让生产者线程和消费者线程分别在不同的谓词条件上进行等待

本例中所有的生产者线程在 notEmpty 谓词条件上等待,所有的消费者线程在 notFull 谓词条件上等待,当队列是满的时候所有的生产者线程阻塞,添加元素之后则唤醒某个消费者线程,此时则不用担心会唤醒消费者线程

lock.lock();
try {
	while(isFull()) {
		// 生产者线程进行阻塞
		notFull.await();
	}
	
	array[index++] = item;
	
	// 唤醒某个消费者线程
	notEmpty.signal();
} catch (InterruptedException e) {
	e.printStackTrace();
} finally {
	lock.unlock();
}

© 著作权归作者所有

w
粉丝 3
博文 15
码字总数 15775
作品 0
深圳
私信 提问
Java多线程 - 各种线程锁

多个线程同时对同一个对象进行读写操作,很容易会出现一些难以预料的问题。所以很多时候我们需要给代码块加锁,同一时刻只允许一个线程对某个对象进行操作。多线程之所以会容易引发一些难以发现...

嘉伟咯
2017/09/01
0
0
线程安全之 ReentrantLock 完全解析

线程互斥同步除了使用最基本的 synchronized 关键字外(关于 synchronized 关键字的实现原理,请看之前写的线程安全之 synchronized 关键字), Java 5 之后还提供了 API 可以实现同样的功能...

JohnnyShieh
2017/10/26
0
0
Java并发编程J.U.C之Condition

在上一篇中,我们了解了下J.U.C的锁的获取与释放的过程,这个过程主要通过在A.Q.S中维持一个等待队列来实现,其中我们也提到了,在A.Q.S中除了一个等待队列之外,还有一个Condition队列,在了...

编走编想
2013/08/23
222
0
详解Condition的await和signal等待/通知机制

1.Condition简介 任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),noti...

你听___
2017/11/12
0
0
java并发库中的阻塞队列--BlockingQueue

1.阻塞队列的概念 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的...

vshcxl
2016/11/28
32
0

没有更多内容

加载失败,请刷新页面

加载更多

可见性有序性,Happens-before来搞定

写在前面 上一篇文章并发 Bug 之源有三,请睁大眼睛看清它们 谈到了可见性/原子性/有序性三个问题,这些问题通常违背我们的直觉和思考模式,也就导致了很多并发 Bug 为了解决 CPU,内存,IO ...

tan日拱一兵
20分钟前
2
0
网络七层模型与TCP/UDP

为了使全球范围内不同的计算机厂家能够相互之间能够比较协调的进行通信,这个时候就有必要建立一种全球范围内的通用协议,以规范各个厂家之间的通信接口,这就是网络七层模型的由来。本文首先...

爱宝贝丶
23分钟前
2
0
Jenkins World 贡献者峰会及专家答疑展位

本文首发于:Jenkins 中文社区 原文链接 作者:Marky Jackson 译者:shunw Jenkins World 贡献者峰会及专家答疑展位 本文为 Jenkins World 贡献者峰会活动期间的记录 Jenkins 15周岁啦!Jen...

Jenkins中文社区
41分钟前
8
0
杂谈:面向微服务的体系结构评审中需要问的三个问题

面向微服务的体系结构如今风靡全球。这是因为更快的部署节奏和更低的成本是面向微服务的体系结构的基本承诺。 然而,对于大多数试水的公司来说,开发活动更多的是将现有的单块应用程序转换为...

liululee
56分钟前
7
0
OSChina 周二乱弹 —— 我等饭呢,你是不是来错食堂了?

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @ 自行车丢了:给主编推荐首歌 《クリスマスの夜》- 岡村孝子 手机党少年们想听歌,请使劲儿戳(这里) @烽火燎原 :国庆快来,我需要长假! ...

小小编辑
今天
625
11

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部