生产者消费者问题是多线程问题中关于线程间通信的比较经典的问题。该问题需要注意的是存在一个缓冲区,当缓冲区满时,生产者不可以加入数据,消费者可以获取数据进行消费;相反,当缓存区为空时,消费者无法消费,但是生产者可以消费。解决生产者消费者问题的方法就是在两者建立线程通信。
1.await()、notify、notifyAll()方法
通过建立一个最大缓存区的最大容量,并且将当前容量与最大容量比较,对于生产者来说,如果当前容量小于最大容量则可以生产,并且唤醒所有等待的线程,否则等待;对于消费者来说,如果当前容量大于0,则可以消费,并且要唤醒所有等待的线程,否则等待。具体代码如下所示:
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;;
public class ProducerAndConsumer {
static LinkedList<Integer> list=new LinkedList<>();
static int max_cap=10;
class Producer implements Runnable{
public void run()
{
synchronized(list) {
if(list.size()<max_cap)//保证缓存区满时不再生产,而是进行等待
{
int num=new Random().nextInt(100);
list.add(num);
System.out.println(Thread.currentThread().getName()+"生产了产品"+num);
list.notifyAll();//唤醒其它等待的线程。
}
else {
try {
System.out.println("当前缓存容量已经满了!不能生产了!");
list.wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
class Consumer implements Runnable{
public void run()
{
synchronized(list) {
if(!list.isEmpty()) {//保证不在缓存区为空时进行消费。
int product=list.poll();
System.out.println(Thread.currentThread().getName()+"消费了产品"+product);
list.notifyAll();
}
else {
try {
System.out.println("当前缓存区为空!无法消费!");
list.wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService es=Executors.newFixedThreadPool(30);
ProducerAndConsumer pc=new ProducerAndConsumer();
for(int i=0;i<15;i++)
{
es.execute(new Thread(pc.new Producer()));
es.execute(new Thread(pc.new Consumer()));
}
}
}
运行结果如下:
pool-1-thread-1生产了产品10
pool-1-thread-2消费了产品10
当前缓存区为空!无法消费!
pool-1-thread-3生产了产品20
pool-1-thread-5生产了产品48
pool-1-thread-6消费了产品20
pool-1-thread-7生产了产品40
pool-1-thread-8消费了产品48
pool-1-thread-10消费了产品40
pool-1-thread-9生产了产品24
pool-1-thread-11生产了产品18
pool-1-thread-12消费了产品24
pool-1-thread-13生产了产品59
pool-1-thread-14消费了产品18
pool-1-thread-15生产了产品47
pool-1-thread-16消费了产品59
pool-1-thread-17生产了产品91
pool-1-thread-18消费了产品47
pool-1-thread-19生产了产品76
pool-1-thread-20消费了产品91
pool-1-thread-21生产了产品57
pool-1-thread-22消费了产品76
pool-1-thread-23生产了产品14
pool-1-thread-24消费了产品57
pool-1-thread-25生产了产品99
pool-1-thread-26消费了产品14
pool-1-thread-27生产了产品5
pool-1-thread-28消费了产品99
pool-1-thread-29生产了产品44
pool-1-thread-30消费了产品5
使用notifyAll()和wait()方法可以保证线程间的通信,从而通过条件限制进行阻塞以及唤醒。
2.阻塞队列解决生产者消费者问题
阻塞队列在非阻塞队列的基础上支持两个附加操作的队列。这两个附加操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。阻塞队列的take()就相当于消费者消费,而put()方法相当于生产者生产。
package concurrent;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;;
public class ProducerAndConsumerII {
static LinkedBlockingQueue<Integer> list=new LinkedBlockingQueue(10);
class Producer implements Runnable{
public void run()
{
for(int i=0;i<30;i++)
{
try {
System.out.println("消费者生产了产品"+i);
list.put(i);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
public void run()
{
while(true)
{
try {
System.out.println("消费者消费了产品"+list.take());
Thread.sleep(50);
}
catch(Exception e)
{
System.out.println("当前无法消费");
}
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService es=Executors.newFixedThreadPool(30);
ProducerAndConsumerII pc=new ProducerAndConsumerII();
new Thread(pc.new Producer()).start();
new Thread(pc.new Consumer()).start();
}
}
运行结果如下:
消费者生产了产品0
消费者生产了产品1
消费者生产了产品2
消费者生产了产品3
消费者生产了产品4
消费者生产了产品5
消费者生产了产品6
消费者生产了产品7
消费者生产了产品8
消费者消费了产品0
消费者生产了产品9
消费者生产了产品10
消费者生产了产品11
消费者消费了产品1
消费者生产了产品12
消费者消费了产品2
消费者生产了产品13
消费者消费了产品3
消费者生产了产品14
消费者消费了产品4
消费者生产了产品15
消费者消费了产品5
消费者生产了产品16
消费者消费了产品6
消费者生产了产品17
消费者消费了产品7
消费者生产了产品18
消费者消费了产品8
消费者生产了产品19
消费者消费了产品9
消费者生产了产品20
消费者生产了产品21
消费者消费了产品10
消费者消费了产品11
消费者生产了产品22
消费者消费了产品12
消费者生产了产品23
消费者消费了产品13
消费者生产了产品24
消费者消费了产品14
消费者生产了产品25
消费者消费了产品15
消费者生产了产品26
消费者消费了产品16
消费者生产了产品27
消费者消费了产品17
消费者生产了产品28
消费者消费了产品18
消费者生产了产品29
消费者消费了产品19
消费者消费了产品20
消费者消费了产品21
消费者消费了产品22
消费者消费了产品23
消费者消费了产品24
消费者消费了产品25
消费者消费了产品26
消费者消费了产品27
消费者消费了产品28
消费者消费了产品29
使用了阻塞队列解决生产者消费者问题时不需要手动建立线程间通信,而是通过take()和put()方法阻塞的性能来解决生产者消费者问题的,不需要手动建立线程通信。