生产者-消费者模式实现(ReentarntLock)
1. 介绍
在JAVA多线程编程中,生产者-消费者模式有多种实现方式,例如:
synchronized
+wait
/notify
- 阻塞队列
BolckingQueue
ReentrantLock
+两个Condition
条件
在这篇随笔中主要考虑利用ReentarntLock
和Condition
实现, 重点是利用中断实现了生产者和消费者线程的执行终止。
2. 代码实现
类文件简介:
Container
提供了向共享队列入队和出队的方法Consumer
封装了消费者任务Producer
封装生产者任务MainTest
执行测试
Container
类实现:
public class Container<T> {
private final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
private final Queue<T> sharedQueue;
private final int capacity;
public Container( Queue<T> sharedQueue, int capacity) {
if(sharedQueue == null){
throw new NullPointerException("sharedQueue is null");
}
if (capacity < 1){
throw new IllegalArgumentException("capacity less one");
}
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.notFull = lock.newCondition();
this.sharedQueue = sharedQueue;
this.capacity = capacity;
}
/**
* 生产者生产操作
* @param element 生产的元素
* @throws InterruptedException 如果当前线程被中断
*/
public void put(T element) throws InterruptedException {
try {
lock.lockInterruptibly();
while (sharedQueue.size() == capacity){
notFull.await();
}
sharedQueue.offer(element);
System.out.println("生产者线程-" + Thread.currentThread().getId()+" 生产: "+element+" 队列元素数量: " + sharedQueue.size());
notEmpty.signal();
}finally {
try{
lock.unlock();
}catch (IllegalMonitorStateException e){}
}
}
/**
* 消费者操作
* @return 返回获得的元素
* @throws InterruptedException 如果当前线程被中断
*/
public T take() throws InterruptedException {
try {
lock.lockInterruptibly();
while (sharedQueue.isEmpty()){
notEmpty.await();
}
T element = sharedQueue.poll();
System.out.println("消费者线程-" + Thread.currentThread().getId()+" 消费 "+ element +" 队列元素数量: " + sharedQueue.size());
notFull.signal();
return element;
}finally{
try{
lock.unlock();
}
catch (IllegalMonitorStateException e){}
}
}
}
对于Container
类有一下几点说明:
- 加锁时使用
lock.lockInterruptibly()
而不是lock.lock()
,使得在竞争锁期间可以响应中断。 take()
和put()
方法向外抛出InterruptedException
是为了在外部调用时跳出死循环,从而实现线程的退出。InterruptedException
异常是由方法内部lockInterruptibly()
和await()
方法抛出的。- 在
finally
语句块中对lock.unlock()
捕获了IllegalMonitorStateException
异常。这是因为当线程在调用lockInterruptibly
后竞争锁失败,进入自选阻塞状态,如果在此时被中断,则在函数返回前执行lock.unlock()
时没有获得锁就会产生IllegalMonitorStateException
异常,所以进行了捕获。另外,当线程调用await()
方法进入阻塞状态时,被中断返回时,是保证一定该线程获得锁的(),所以不会此种情况导致lock.unlock()
抛出IllegalMonitorStateException
异常。 await()
方法返回线程一定获得该条件所属的锁(OpenJDK8 Condition#await() 部分注释)In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.
Consumer
和Producer
类实现
两个类实现比较类似就一起介绍
Consumer
类:
public class Consumer implements Runnable{
private final Container<Integer> container;
public Consumer(Container<Integer> container) {
this.container = container;
}
@Override
public void run() {
try{
while (true){
// 模拟消费耗时
// Thread.sleep(new Random().nextInt(100)+50);
Integer element = container.take();
// doSomething
}
}// InterruptedException 异常抛出有两处, Thread.sleep()方法、container.take()方法
catch (InterruptedException e){
System.out.println("消费者线程-" + Thread.currentThread().getId()+" 执行完毕");
}
}
}
Producer
类:
public class Producer implements Runnable{
private final Container<Integer> container;
public Producer(Container<Integer> container) {
this.container = container;
}
@Override
public void run() {
try{
while (true){
Random random = new Random();
//sleep模拟生产产品的耗时
// Thread.sleep(new Random().nextInt(100)+50);
int element = random.nextInt(1000);
container.put(element);
}
}// InterruptedException异常抛出有两处: Thread.sleep()方法、container.put()方法
catch (InterruptedException e){
System.out.println("生产者线程-" + Thread.currentThread().getId()+" 执行完毕");
}
}
}
测试类MainTest
实现
MainTest
代码:
public class MainTest {
public static void main(String[] args){
Container<Integer> container = new Container<>(new ArrayDeque<>(), 5);
//为了使所有提交任务都能及时执行,使用newCachedThreadPool()方法创建线程池
ExecutorService service = Executors.newCachedThreadPool();
int consumerCount = 5; //消费者线程数
for (int i = 0; i < consumerCount; i++) {
service.execute(new Consumer(container));
}
int producerCount = 4; //生产者线程数
for (int i = 0; i < producerCount; i++) {
service.execute(new Producer(container));
}
//阻塞主线程 一段时间
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//关闭线程池
service.shutdownNow();
boolean isTerminated = false;
while (!isTerminated){
try {
isTerminated = service.awaitTermination(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程退出");
}
}
MainTest
中需要注意的是关闭线程池调用的函数是ExecutorService.shutdownNow()
而不是ExecutorService.shutdown()
。调用shutdownNow()
后线程池进入STOP
状态,此时会中断所以线程(包括正在执行的线程),清空线程池的工作队列;而调用shutdown()
后线程池进入SHUTDOWN
状态,在此状态下只会中断空闲线程,不中断正在执行的线程,所以如果调用shutdown()
在该程序会一直运行,不会停下来。
3. 运行结果
- 其中一次运行结果(部分)为:
生产者线程-19 生产: 686 队列元素数量: 2
生产者线程-19 生产: 865 队列元素数量: 3
生产者线程-19 生产: 346 队列元素数量: 4
生产者线程-19 生产: 191 队列元素数量: 5
消费者线程-13 消费 899 队列元素数量: 4
消费者线程-13 消费 686 队列元素数量: 3
消费者线程-13 消费 865 队列元素数量: 2
消费者线程-16 消费 346 队列元素数量: 1
消费者线程-16 消费 191 队列元素数量: 0
生产者线程-20 生产: 56 队列元素数量: 1
生产者线程-20 生产: 203 队列元素数量: 2
生产者线程-20 生产: 902 队列元素数量: 3
.......
.......
.......
生产者线程-17 生产: 180 队列元素数量: 1
生产者线程-17 生产: 550 队列元素数量: 2
生产者线程-17 生产: 925 队列元素数量: 3
消费者线程-13 消费 180 队列元素数量: 2
消费者线程-14 消费 550 队列元素数量: 1
生产者线程-17 执行完毕
生产者线程-18 生产: 722 队列元素数量: 2
生产者线程-20 生产: 998 队列元素数量: 3
消费者线程-12 消费 925 队列元素数量: 2
消费者线程-14 执行完毕
生产者线程-18 执行完毕
消费者线程-13 执行完毕
生产者线程-20 执行完毕
消费者线程-15 消费 722 队列元素数量: 1
消费者线程-12 执行完毕
生产者线程-19 生产: 971 队列元素数量: 2
消费者线程-16 消费 998 队列元素数量: 1
消费者线程-15 执行完毕
生产者线程-19 执行完毕
消费者线程-16 执行完毕
主线程退出
4. 补充测试
在MainTest
测试中使用了线程池管理线程,并中断线程。如果不使用线程池而直接使用new Thread()
的方法生产线程,则可以按照如下代码MainTest2
测试:
public class MainTest2 {
public static void main(String[] args) {
Container<Integer> container = new Container<>(new ArrayDeque<>(), 5);
//定义消费者线程
Runnable consumer = new Consumer(container);
int consumerCount = 5;
List<Thread> consumerThreads = new LinkedList<>();
for (int i = 0; i < consumerCount; i++) {
Thread thread = new Thread(consumer);
consumerThreads.add(thread);
thread.start();
}
//定义生产者线程
Runnable producer= new Producer(container);
int producerCount = 4;
List<Thread> producerThreads = new LinkedList<>();
for (int i = 0; i < producerCount; i++) {
Thread thread = new Thread(producer);
producerThreads.add(thread);
thread.start();
}
//阻塞主线程 一段时间
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过主线程依次中断生产者和消费者线程
for (Thread consumerThread : consumerThreads) {
consumerThread.interrupt();
}
for (Thread producerThread : producerThreads) {
producerThread.interrupt();
}
//等待线程全部执行结束后主线程才退出
boolean isRunning = true;
while (isRunning){
isRunning = false;
for (Thread consumerThread : consumerThreads) {
isRunning = isRunning || consumerThread.isAlive();
}
for (Thread producerThread : producerThreads) {
isRunning = isRunning || producerThread.isAlive();
}
}
System.out.println("主线程退出");
}
}
5. 参考:
本文主要目的是记录学习过程,加深对知识点理解; 如有行文有误,望指正。