生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。解决生产者/消费者问题的方法可分为两类:(1)采用某种机制保护生产者和消费者之间的同步;(2)在生产者和消费者之间建立一个管道。第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题。
同步方法有三种:wait()/notify()、await()/signal()和BlockingQueue队列
本文只讲解await()/signal()和BlockingQueue队列
1、await()/signal()方法
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Storage {//缓冲区类
private LinkedList<Object> list=new LinkedList<>();
private int maxSize=10;
private ReentrantLock lock=new ReentrantLock();
private Condition notEmpty=lock.newCondition();
private Condition notFull=lock.newCondition();
public void setMaxSize(int maxSize){
this.maxSize=maxSize;
}
public void produce(){
try {
lock.lockInterruptibly();
while(list.size()==maxSize){
System.out.println("仓库已满,暂时无法执行生产任务");
notFull.await();
}
list.add(new Object());
System.out.println(Thread.currentThread().getName()
+"生产了一个产品,现在仓库储量为:"+list.size());
notEmpty.signalAll();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
lock.unlock();
}
}
public void consume(){
try {
lock.lockInterruptibly();
while(list.size()==0){
System.out.println("仓库已空,暂时无法执行消费任务");
notEmpty.await();
}
list.remove();
System.out.println(Thread.currentThread().getName()
+"消费了一个产品,现在仓库储量为:"+list.size());
notFull.signalAll();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
lock.unlock();
}
}
}
import java.util.Random;
public class Producer implements Runnable{
private Storage storage;
private boolean isRunning=true;
public Producer(Storage storage){
this.storage=storage;
}
public void run(){
try {
while(isRunning){
Thread.sleep(new Random().nextInt(1000));
storage.produce();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void stop(){
isRunning=false;
}
}
import java.util.Random;
public class Consumer implements Runnable{
private Storage storage;
private boolean isRunning=true;
public Consumer(Storage storage){
this.storage=storage;
}
@Override
public void run() {
try{
while(isRunning){
Thread.sleep(new Random().nextInt(1000));
storage.consume();
}
}
catch(InterruptedException e){
e.printStackTrace();
}
}
public void stop(){
isRunning=false;
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
Storage storage=new Storage();
Producer producer1=new Producer(storage);
Producer producer2=new Producer(storage);
Producer producer3=new Producer(storage);
Consumer consumer1=new Consumer(storage);
Consumer consumer2=new Consumer(storage);
Consumer consumer3=new Consumer(storage);
ExecutorService exe=Executors.newCachedThreadPool();
exe.execute(producer1);
exe.execute(producer2);
exe.execute(producer3);
exe.execute(consumer1);
exe.execute(consumer2);
exe.execute(consumer3);
exe.shutdown();
}
}
部分运行结果:
pool-1-thread-6消费了一个产品,现在仓库储量为:3
pool-1-thread-5消费了一个产品,现在仓库储量为:2
pool-1-thread-4消费了一个产品,现在仓库储量为:1
pool-1-thread-6消费了一个产品,现在仓库储量为:0
仓库已空,暂时无法执行消费任务
pool-1-thread-3生产了一个产品,现在仓库储量为:1
pool-1-thread-4消费了一个产品,现在仓库储量为:0
pool-1-thread-3生产了一个产品,现在仓库储量为:1
pool-1-thread-1生产了一个产品,现在仓库储量为:2
pool-1-thread-2生产了一个产品,现在仓库储量为:3
pool-1-thread-5消费了一个产品,现在仓库储量为:2
pool-1-thread-5消费了一个产品,现在仓库储量为:1
pool-1-thread-4消费了一个产品,现在仓库储量为:0
pool-1-thread-3生产了一个产品,现在仓库储量为:1
pool-1-thread-4消费了一个产品,现在仓库储量为:0
仓库已空,暂时无法执行消费任务
仓库已空,暂时无法执行消费任务
pool-1-thread-1生产了一个产品,现在仓库储量为:1
pool-1-thread-4消费了一个产品,现在仓库储量为:0
仓库已空,暂时无法执行消费任务
仓库已空,暂时无法执行消费任务
仓库已空,暂时无法执行消费任务
pool-1-thread-1生产了一个产品,现在仓库储量为:1
2、BlockingQueue队列
import java.util.Random;
import java.util.concurrent.BlockingQueue;
public class Producer_Queue implements Runnable{
private BlockingQueue<Object> queue;
private boolean isRunning=true;
public Producer_Queue(BlockingQueue<Object> queue){
this.queue=queue;
}
public synchronized void run(){
while(isRunning){
try {
Thread.sleep(new Random().nextInt(1000));
queue.put(new Object());
System.out.println(Thread.currentThread().getName()+
"生产了一个产品,现仓库储量为:"+queue.size());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void stop(){
isRunning=false;
}
}
import java.util.Random;
import java.util.concurrent.*;
public class Consumer_Queue implements Runnable{
private BlockingQueue<Object> queue;
private boolean isRunning=true;
public Consumer_Queue(BlockingQueue<Object> queue){
this.queue=queue;
}
public void run(){
while(isRunning){
try {
Thread.sleep(new Random().nextInt(1000));
queue.take();
System.out.println(Thread.currentThread().getName()+
"消费了一个产品,现仓库储量为:"+queue.size());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void stop(){
isRunning=false;
}
}
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class BlcokingQueue_PS {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Object> queue=new LinkedBlockingQueue<>(5);
Consumer_Queue c1=new Consumer_Queue(queue);
Consumer_Queue c2=new Consumer_Queue(queue);
Consumer_Queue c3=new Consumer_Queue(queue);
Producer_Queue p1=new Producer_Queue(queue);
Producer_Queue p2=new Producer_Queue(queue);
Producer_Queue p3=new Producer_Queue(queue);
ExecutorService exe=Executors.newCachedThreadPool();
exe.execute(c1);
exe.execute(c2);
exe.execute(c3);
exe.execute(p1);
exe.execute(p2);
exe.execute(p3);
Thread.sleep(2000);
p1.stop();
p2.stop();
p3.stop();
c1.stop();
c2.stop();
c3.stop();
exe.shutdown();
}
}
运行结果:
pool-1-thread-4生产了一个产品,现仓库储量为:0
pool-1-thread-2消费了一个产品,现仓库储量为:0
pool-1-thread-5生产了一个产品,现仓库储量为:1
pool-1-thread-2消费了一个产品,现仓库储量为:0
pool-1-thread-6生产了一个产品,现仓库储量为:1
pool-1-thread-1消费了一个产品,现仓库储量为:0
pool-1-thread-4生产了一个产品,现仓库储量为:1
pool-1-thread-3消费了一个产品,现仓库储量为:0
pool-1-thread-4生产了一个产品,现仓库储量为:1
pool-1-thread-2消费了一个产品,现仓库储量为:0
pool-1-thread-5生产了一个产品,现仓库储量为:1
pool-1-thread-3消费了一个产品,现仓库储量为:0
pool-1-thread-6生产了一个产品,现仓库储量为:1
pool-1-thread-2消费了一个产品,现仓库储量为:0
pool-1-thread-4生产了一个产品,现仓库储量为:1
pool-1-thread-1消费了一个产品,现仓库储量为:0
pool-1-thread-4生产了一个产品,现仓库储量为:1
pool-1-thread-5生产了一个产品,现仓库储量为:2
pool-1-thread-6生产了一个产品,现仓库储量为:3
pool-1-thread-4生产了一个产品,现仓库储量为:4
pool-1-thread-3消费了一个产品,现仓库储量为:3
pool-1-thread-2消费了一个产品,现仓库储量为:2
pool-1-thread-1消费了一个产品,现仓库储量为:1
有时使用BlockingQueue可能会出现put()和System.out.println()输出不匹配的情况,这是由于它们之间没有同步造成的。由于生产者在调用put()生产产品之后,还没来得及执行println(),消费者就调用take()方法消费掉了产品。