关于等待通知机制,在Java中主要有两种方式。一种是基于wait/notify方法集合synchronized关键字实现的,这在上一篇文章《Java并发与锁设计实现详述(10)- Java中的等待/通知机制》中已经讲述了,另一种方法是Condition结合Lock来实现。
下面先把这两种方式进行一个简单的对比。
对比项 | Object Monitor Methods | Condition |
---|---|---|
前置条件 | 在使用之前获取对象的锁 | 调用Lock.lock获取锁; 调用Lock.newCondition获取Condition对象; |
调用方式 | 直接调用,如:object.wait() | 直接调用,如condition.await() |
等待队列个数 | 一个 | 多个 |
当前线程释放锁并进入等待状态 | 支持 | 支持 |
当前线程释放锁并进入等待状态, 在等待状态不响应中断 | 不支持 | 支持 |
当前线程释放锁并进入超时等待状态 | 支持 | 支持 |
当前线程释放锁并进入超时等待状态, 在超时等待状态不响应中断 | 不支持 | 支持 |
唤醒等待队列中的一个线程 | 支持 | 支持 |
唤醒等待队列中的全部线程 | 支持 | 支持 |
从上面的对比来看,两种方式功能基本一样,只是在响应中断的场景下有细微区别。
由于前文已经讲过基于Object Monitor Methods实现的等待通知机制,在本文将讲述另一种实现,即基于Condition接口的实现方式。
Condition的接口与示例:
Condition接口定义了等待/通知两种类型的方法,在线程调用这些方法时,需要提前获取Condition对象关联的锁(在基于wait/notify方法实现的方案中需要获取的是对象锁)。
Condition对象是需要关联Lock对象的,经调用Lock对象的newCondition()对象创建而来,也就是说Condition的使用是需要依赖Lock对象的。
下面是使用Condition的示范代码:
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void conditionAwait() throws InterruptedException {
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() {
lock.lock();
try {
condition.signal();//condition.signalAll();
} finally {
lock.unlock();
}
}
下面是关于Condition方法的一些简单说明:
方法名称 | 描述 |
---|---|
await() | 当前线程进入等待状态直到被通知(signal)或者中断; 当前线程进入运行状态并从await()方法返回的场景包括: (1)其他线程调用相同Condition对象的signal/signalAll方法,并且当前线程被唤醒; (2)其他线程调用interrupt方法中断当前线程; |
awaitUninterruptibly() | 当前线程进入等待状态直到被通知,在此过程中对中断信号不敏感,不支持中断当前线程 |
awaitNanos(long) | 当前线程进入等待状态,直到被通知、中断或者超时。如果返回值小于等于0,可以认定就是超时了 |
awaitUntil(Date) | 当前线程进入等待状态,直到被通知、中断或者超时。如果没到指定时间被通知,则返回true,否则返回false |
signal() | 唤醒一个等待在Condition上的线程,被唤醒的线程在方法返回前必须获得与Condition对象关联的锁 |
signalAll() | 唤醒所有等待在Condition上的线程,能够从await()等方法返回的线程必须先获得与Condition对象关联的锁 |
下面通过一个示例来简单看看Condition是如何实现线程等待/通知机制的。
package com.majing.java.concurrent;
import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedQueue {
private Integer[] items;//定义为数组,在创建对象时就确定容量
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
private int count;
private int addIndex,removeIndex;
public BoundedQueue(int size){
items = new Integer[size];
}
public void add(Integer object) throws InterruptedException{
lock.lock();
try{
while(count==items.length){
notFull.await();
}
items[addIndex] = object;
if(++addIndex==items.length){
addIndex = 0;
}
count++;
System.out.println(Thread.currentThread()+" 插入一个元素,数组为:"+Arrays.toString(items));
notEmpty.signal();
}finally{
lock.unlock();
}
}
@SuppressWarnings("unchecked")
public Integer remove() throws InterruptedException{
lock.lock();
try{
while(count==0){
notEmpty.await();
}
Integer temp = items[removeIndex];
items[removeIndex] = null;
System.out.println(Thread.currentThread()+" 移除一个元素,数组为:"+Arrays.toString(items));
if(++removeIndex==items.length){
removeIndex=0;
}
count--;
notFull.signal();
return temp;
}finally{
lock.unlock();
}
}
}
上面是有界队列实现,下面写个简单的测试用例来测试这个有界队列是否可靠。
package com.majing.java.test;
import java.util.Random;
import com.majing.java.concurrent.BoundedQueue;
public class Test {
private static final Random random = new Random(System.currentTimeMillis());
public static void main(String[] args) throws InterruptedException {
BoundedQueue queue = new BoundedQueue(5);
for(int i=1;i<=20;i++){
Thread thread = new Thread(new Producter(queue),String.valueOf(i));
thread.start();
}
for(int i=1;i<=20;i++){
Thread thread = new Thread(new Consumer(queue),String.valueOf(i));
thread.start();
}
}
static class Producter implements Runnable{
private BoundedQueue queue;
public Producter(BoundedQueue queue){
this.queue = queue;
}
public void produce() throws InterruptedException{
queue.add(new Integer(random.nextInt(100)));
}
@Override
public void run() {
try {
produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Consumer implements Runnable{
private BoundedQueue queue;
public Consumer(BoundedQueue queue){
this.queue = queue;
}
public Integer remove() throws InterruptedException{
return queue.remove();
}
@Override
public void run() {
try {
remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行上面的代码,结果如下:
Thread[1,5,main] 插入一个元素,数组为:[92, null, null, null, null]
Thread[2,5,main] 插入一个元素,数组为:[92, 87, null, null, null]
Thread[3,5,main] 插入一个元素,数组为:[92, 87, 19, null, null]
Thread[4,5,main] 插入一个元素,数组为:[92, 87, 19, 88, null]
Thread[5,5,main] 插入一个元素,数组为:[92, 87, 19, 88, 22]
Thread[1,5,main] 移除一个元素,数组为:[null, 87, 19, 88, 22]
Thread[2,5,main] 移除一个元素,数组为:[null, null, 19, 88, 22]
Thread[6,5,main] 插入一个元素,数组为:[23, null, 19, 88, 22]
Thread[7,5,main] 插入一个元素,数组为:[23, 26, 19, 88, 22]
Thread[3,5,main] 移除一个元素,数组为:[23, 26, null, 88, 22]
Thread[4,5,main] 移除一个元素,数组为:[23, 26, null, null, 22]
Thread[9,5,main] 插入一个元素,数组为:[23, 26, 30, null, 22]
Thread[8,5,main] 插入一个元素,数组为:[23, 26, 30, 83, 22]
Thread[5,5,main] 移除一个元素,数组为:[23, 26, 30, 83, null]
Thread[6,5,main] 移除一个元素,数组为:[null, 26, 30, 83, null]
Thread[7,5,main] 移除一个元素,数组为:[null, null, 30, 83, null]
Thread[10,5,main] 插入一个元素,数组为:[null, null, 30, 83, 25]
Thread[11,5,main] 插入一个元素,数组为:[13, null, 30, 83, 25]
Thread[12,5,main] 插入一个元素,数组为:[13, 30, 30, 83, 25]
Thread[9,5,main] 移除一个元素,数组为:[13, 30, null, 83, 25]
Thread[8,5,main] 移除一个元素,数组为:[13, 30, null, null, 25]
Thread[13,5,main] 插入一个元素,数组为:[13, 30, 9, null, 25]
Thread[14,5,main] 插入一个元素,数组为:[13, 30, 9, 4, 25]
Thread[10,5,main] 移除一个元素,数组为:[13, 30, 9, 4, null]
Thread[15,5,main] 插入一个元素,数组为:[13, 30, 9, 4, 85]
Thread[11,5,main] 移除一个元素,数组为:[null, 30, 9, 4, 85]
Thread[13,5,main] 移除一个元素,数组为:[null, null, 9, 4, 85]
Thread[16,5,main] 插入一个元素,数组为:[20, null, 9, 4, 85]
Thread[18,5,main] 插入一个元素,数组为:[20, 71, 9, 4, 85]
Thread[14,5,main] 移除一个元素,数组为:[20, 71, null, 4, 85]
Thread[17,5,main] 插入一个元素,数组为:[20, 71, 50, 4, 85]
Thread[16,5,main] 移除一个元素,数组为:[20, 71, 50, null, 85]
Thread[19,5,main] 插入一个元素,数组为:[20, 71, 50, 3, 85]
Thread[17,5,main] 移除一个元素,数组为:[20, 71, 50, 3, null]
Thread[18,5,main] 移除一个元素,数组为:[null, 71, 50, 3, null]
Thread[19,5,main] 移除一个元素,数组为:[null, null, 50, 3, null]
Thread[20,5,main] 插入一个元素,数组为:[null, null, 50, 3, 8]
Thread[20,5,main] 移除一个元素,数组为:[null, null, null, 3, 8]
Thread[12,5,main] 移除一个元素,数组为:[null, null, null, null, 8]
Thread[15,5,main] 移除一个元素,数组为:[null, null, null, null, null]
在这个例子中,实现了一个有界队列,并且这个有界队列的容量我控制在了5个元素,然后通过20个生产者和20个消费者并发去添加和取出元素,从运行结果来看,队列并没有出现异常,生产者和消费者能够相互协调有序的进行。
关于Condition的内部实现和原理,这里先不做说明了,后面如果有时间会另行补充进该博文,想知道的朋友可以自己去查看下源码.
感谢大家的阅读,如果有对Java编程、中间件、数据库、及各种开源框架感兴趣,欢迎关注我的博客和头条号(源码帝国),博客和头条号后期将定期提供一些相关技术文章供大家一起讨论学习,谢谢。
如果觉得文章对您有帮助,欢迎给我打赏,一毛不嫌少,一百不嫌多,^_^谢谢。