生产者-消费者问题是一个经典的线程同步问题。可以理解为:生产者生产出一个产品,将其放入缓冲区,消费者从缓冲区取走产品之后,生产者继续生产产品。注意的是,生产者必须等待消费者取走产品之后才继续生产,消费者也必须在缓冲区有产品是才消费。
一、首先,最简单的模型就是单生产者,单消费者,单次生产消费
public class Resource {
private String name;
private boolean flag;
private int count=1;
public void setResource(String name){
this.name = name + "---" +count;
count++;
System.out.println(Thread.currentThread().getName()+"生产者"+this.name);
}
public void getResource(){
System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
}
}
public class Producer implements Runnable {
private Resource rec; //这一步是为了保证生产者和消费者操作的资源是一样的
public Producer(Resource rec){
this.rec=rec;
}
public void run(){
rec.setResource("面包");
}
}
public class Customer implements Runnable{
private Resource res;
Customer(Resource res){
this.res=res;
}
public void run(){
res.getResource();
}
}
public class ProducerCustomer {
public static void main(String[] args) {
Resource res = new Resource();
Producer pro = new Producer(res);
Customer cus = new Customer(res);
Thread th1 = new Thread(pro);
Thread th2 = new Thread(cus);
th1.start();
th2.start();
}
}
结果是:
Thread-0生产者面包---1
Thread-1消费者面包---1
意思就是生产者生产一个产品,消费者就消费一个产品
二、单生产者单消费者多次生产消费
那么该如何实现多生产者多消费者呢?可以在生产者和消费者的run函数里面加上while(true)的判断,但是这样就会产生消费者重复消费或者生产者重复生产的问题,如下图。原因就是多线程的不安全性,那么就可以用同步来解决该问题。
当时当给生产者和消费者加上同步之后,publicsynchronizedvoid setResource(String name);public synchronized void getResource(),会发现又产生了生产者持续多生产和消费者持续多消费的问题,如下图
该问题是由于生产者或消费者不断拿到锁,可以不断生产或消费。那么可以在拿到锁之后,加上判断的一步,如果没有产品才生产,有产品才消费。这就可以引入等待唤醒机制。
wait():该方法可以让线程处于冻结状态,并将线程先临时存储到线程池
notify():唤醒指定线程池中的任意一个线程
notifyAll(): 唤醒指定线程池中的所有线程
注意:1)这些方法必须用在同步中,因为它们是同来操作同步锁上的线程状态的。
2)在使用这些方法时,必须标识它们所属于的锁,标识的方式就是:锁对象.wait()/锁对象.notify/锁对象.notifyAll,相同锁的notify()可以获取相同锁的wait()。(wait方法等在API中查看的时候,它是属于object的方法的)
完整程序如下:
public class Resource {
private String name;
private boolean flag;
private int count=1;
public synchronized void setResource(String name){
if(flag){
try{
wait(); //查阅API可以知道,wait方法需要抛出InterruptedException异常,在该段代码中,wait方法对应的锁是默认的this,可以不写
}
catch(InterruptedException e){
}
}
this.name = name + "---" +count;
count++;
System.out.println(Thread.currentThread().getName()+"生产者"+this.name);
flag = true;
notify();
}
public synchronized void getResource(){
if(!flag){ //在生产者或消费者拿到锁之后进行判断,是否是继续生产消费还是进入等待状态。
try{
wait();
}
catch(InterruptedException e){
}
}
System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
flag=false;
notify();
}
}
public class Producer implements Runnable {
private Resource rec;
public Producer(Resource rec){
this.rec=rec;
}
public void run(){
while(true)
rec.setResource("面包");
}
}
public class Customer implements Runnable{
private Resource res;
Customer(Resource res){
this.res=res;
}
public void run(){
while(true)
res.getResource();
}
}
public class ProducerCustomer {
public static void main(String[] args) {
Resource res = new Resource();
Producer pro = new Producer(res);
Customer cus = new Customer(res);
Thread th1 = new Thread(pro);
Thread th2 = new Thread(cus);
th1.start();
th2.start();
}
}
三、多生产者多消费者多次生产消费
首先,肯定是要开启多个线程来产生多个生产者和多个消费者,这样有可能会产生被唤醒的线程没有再次判断标记就开始工作,导致重复生产和重复消费的问题。所以可以把if 判断改为while判断,同时在上述代码中的notify()要改成notifyAll(),如果不改的话,会出现程序在运行过程中,所有线程都处于冻结状态,也就是死锁。(如何理解--->本方线程在唤醒时,又一次唤醒了本方线程。而本方线程循环判断标记时,又一次继续等待,导致所有线程都处于冻结状态)
完整代码如下:
public class Resource {
private String name;
private boolean flag;
private int count=1;
public synchronized void setResource(String name){
while(flag){ //循环判标记
try{
wait();//wait()方法
}
catch(InterruptedException e){
}
}
this.name = name + "---" +count;
count++;
System.out.println(Thread.currentThread().getName()+"生产者"+this.name);
flag = true;
notifyAll();
}
public synchronized void getResource(){
while(!flag){
try{
wait();
}
catch(InterruptedException e){
}
}
System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
flag=false;
notifyAll();
}
}
public class Producer implements Runnable {
private Resource rec;
public Producer(Resource rec){
this.rec=rec;
}
public void run(){
while(true)
rec.setResource("面包");
}
}
public class Customer implements Runnable{
private Resource res;
Customer(Resource res){
this.res=res;
}
public void run(){
while(true)
res.getResource();
}
}
public class ProducerCustomer {
public static void main(String[] args) {
Resource res = new Resource();
Producer pro = new Producer(res);
Customer cus = new Customer(res);
Thread th0 = new Thread(pro); //开启多个线程
Thread th1 = new Thread(pro);
Thread th2 = new Thread(cus);
Thread th3 = new Thread(cus);
th0.start();
th1.start();
th2.start();
th3.start();
}
}
结果是