文章目录
更多关于Java并发编程的文章请点击这里:Java并发编程实践(0)-目录页
Condition在Java并发编程中是一个十分常用的接口,被称为条件变量,常与显式锁和可重入锁一起使用,它可以在某些条件下使线程进行休眠或者唤醒,本文将以实现生产者-消费者模式的队列作为demo让大家对条件变量 有初步的了解。
一、并发编程中的条件变量
1.1、从生产者-消费者模型理解条件变量
生产者-消费者模型是一种常见的模型,在《Java并发编程实践》中有一个例子很好地解释了这种模式:
两个洗盘子的劳动分工也是一个与生产者-消费者设计相类似的例子:一个人洗盘子,并把洗好的盘子放到盘子架上,另一个人从盘子架上得到盘子,并把它烘干。在这个场景中,盘子架充当了阻塞队列;如果架子上没有盘子,消费者会一直等待,直到有盘子需要烘干,如果盘子架被放满了,那么生产者会停止洗盘子,直到架上有新的空间可用。
在这个例子中哪里看出了条件变量呢?
这个条件变量就是盘子架是否满了,当盘子架满了,那么生产者等待盘子架有空余的空间时才开始工作,当盘子架空了,消费者等待其有碗了才开始工作,这就可以很好地理解条件变量了:条件变量就是在某些条件下使线程进行休眠或者唤醒的一种工作机制。
1.2、Condition接口
Condition接口
是Java并发编程中的环境变量,它位于java.util.concurrent.locks包下,常与显式锁一起使用,使用Lock.newCondition()获得Condition对象。
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
void signal();
void signalAll();
}
1.3、Condition接口方法
以上是Condition接口定义的方法,await
对应于Object.wait
,signal
对应于Object.notify
,signalAll
对应于Object.notifyAll
。特别说明的是Condition的接口改变名称就是为了避免与Object中的wait/notify/notifyAll的语义和使用上混淆,因为Condition同样有wait/notify/notifyAll方法。
- await()方法:
造成当前线程在接到信号或被中断之前一直处于等待状态。 - signal()方法:
唤醒特定的等待线程。 - signalAll()方法:
唤醒所有的等待线程。
二、实现一个生产者-消费者中的条件队列
2.1、条件变量的一般使用模式
lock.lock();
try{
while(条件判断){
acondition.await();
}
//dosomething...
bconditon.signal();
}finally{
lock.unlock();
}
2.2、使用条件变量实现一个生产者-消费者模式的队列
- MyBlockingQueue
package cocurrency.product_and_consume_model;
import org.junit.Test;
import java.util.LinkedList;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Auther: ARong
* @Date: 2019/11/30 2:06 下午
* @Description: 使用可重入锁和条件变量来实现生产消费者模型队列
*/
public class MyBlockingQueue {
private Lock lock = new ReentrantLock();
private Condition isEmpty = lock.newCondition();
private Condition isFull = lock.newCondition();
// 存放数据的队列
private LinkedList<Integer> queue = new LinkedList<>();
// 最大长度
private volatile int maxSize;
public MyBlockingQueue(){
this.maxSize = 1 << 4;
}
public MyBlockingQueue(int maxSize) {
this.maxSize = maxSize;
}
/*
* @Author ARong
* @Description 获取数据
* @Date 2019/11/30 2:17 下午
* @Param []
* @return int
**/
public int poll() {
Integer value = -1;
lock.lock();//执行到该处的线程获得锁
try {
while (queue.size() == 0){
//队列为空,让线程在 isEmpty 这个条件变量中等待
System.out.println(Thread.currentThread().getName()+"[队列为空,poll方法阻塞]");
isEmpty.await();
}
value = queue.poll();
System.out.println(Thread.currentThread().getName()+"[删除元素],当前空余容量为:"+(maxSize - queue.size()));
//唤醒在 isFull 条件变量下等待的线程
isFull.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
return value;
}
/*
* @Author ARong
* @Description 放入数据
* @Date 2019/11/30 2:17 下午
* @Param [data]
* @return void
**/
public void offer(int data) {
// 获取锁
lock.lock();
try {
while (queue.size() == maxSize) {
// 队列已满,让该线程在 isFull条件中 等待
System.out.println(Thread.currentThread().getName()+"[队列已满,offer方法阻塞]");
isFull.await();
}
// 添加数据
queue.offer(data);
System.out.println(Thread.currentThread().getName() + "[添加数据],当前空余容量为:"+(maxSize - queue.size()));
// 唤醒在isEmpty条件等待的线程
isEmpty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
}
- TestBlockingQueue
package cocurrency.product_and_consume_model;
import cocurrency.CountDownLatchDemo;
import org.junit.Test;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Auther: ARong
* @Date: 2019/11/30 4:46 下午
* @Description:
*/
public class TestBlockingQueue {
@Test
public void test() throws InterruptedException {
// 线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 0l, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
// 创建一个只能容纳5个元素的阻塞队列
MyBlockingQueue blockingQueue = new MyBlockingQueue(5);
// 闭锁,等待两个线程完成任务才终止主线程
CountDownLatch countDownLatch = new CountDownLatch(2);
// 创建调用者去offer和poll
threadPoolExecutor.submit(() -> {
// 快速添加元素
for (int i = 0; i < 10; i++) {
blockingQueue.offer(i);
}
countDownLatch.countDown();
});
threadPoolExecutor.submit(() -> {
// 缓慢获取数据
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(200l);
} catch (InterruptedException e) {
e.printStackTrace();
}
blockingQueue.poll();
}
countDownLatch.countDown();
});
// main线程等待
countDownLatch.await();
System.out.println("[任务执行完毕]");
}
}
执行结果:
pool-1-thread-1[添加数据],当前空余容量为:4
pool-1-thread-1[添加数据],当前空余容量为:3
pool-1-thread-1[添加数据],当前空余容量为:2
pool-1-thread-1[添加数据],当前空余容量为:1
pool-1-thread-1[添加数据],当前空余容量为:0
pool-1-thread-1[队列已满,offer方法阻塞]
pool-1-thread-2[删除元素],当前空余容量为:1
pool-1-thread-1[添加数据],当前空余容量为:0
pool-1-thread-1[队列已满,offer方法阻塞]
pool-1-thread-2[删除元素],当前空余容量为:1
pool-1-thread-1[添加数据],当前空余容量为:0
pool-1-thread-1[队列已满,offer方法阻塞]
pool-1-thread-2[删除元素],当前空余容量为:1
pool-1-thread-1[添加数据],当前空余容量为:0
pool-1-thread-1[队列已满,offer方法阻塞]
pool-1-thread-2[删除元素],当前空余容量为:1
pool-1-thread-1[添加数据],当前空余容量为:0
pool-1-thread-1[队列已满,offer方法阻塞]
pool-1-thread-2[删除元素],当前空余容量为:1
pool-1-thread-1[添加数据],当前空余容量为:0
pool-1-thread-2[删除元素],当前空余容量为:1
pool-1-thread-2[删除元素],当前空余容量为:2
pool-1-thread-2[删除元素],当前空余容量为:3
pool-1-thread-2[删除元素],当前空余容量为:4
pool-1-thread-2[删除元素],当前空余容量为:5
[任务执行完毕]