生产者消费者问题是经典的多线程问题,是工业化生产的模型,今天讲的就是生产者,消费者模型以及如何慢慢的改进,进而变为多消费者,多生产者。首先从简单的开始做起,假设我们只有一个生产者,一个消费者。代码实现如下
plate代表着共享区,生产者和消费者可以同时操作共享区,分别生产蛋和消费蛋
**
* Created by lin on 2018/9/15.
*/
public class Plate {
private List<Object> eggs = new ArrayList<>();
private int capacity;
public Plate(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("容量不能小于0");
}
this.capacity = capacity;
}
public Object getEgg() {
System.out.println("消费者取得蛋");
Object egg = eggs.get(0);
eggs.remove(0);
return egg;
}
public void addEgg(Object egg) {
System.out.println("生产者生产蛋");
eggs.add(egg);
}
public int getEggNum() {
return eggs.size();
}
public boolean isFull() {
return this.eggs.size() >= this.capacity;
}
public boolean isEmpty() {
return this.eggs.isEmpty();
}
}
package Inter;
/** 消费者,
* Created by lin on 2018/9/15.
*/
public class Consumer implements Runnable {
private Plate plate;
public Consumer(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
while(true)
synchronized (plate) {
//当容量小于0时,进入等待队列
if (plate.isEmpty()) {
try {
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
plate.getEgg();
plate.notify();
}
}
}
}
package Inter;
/** 生产者
* Created by lin on 2018/9/15.
*/
public class Producer implements Runnable {
private Plate plate;
public Producer(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
while(true)
synchronized (plate) {
if (plate.isFull()) {
try {
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object egg = new Object();
plate.addEgg(egg);
plate.notify();
}
}
}
}
测试类如下
/**
* Created by lin on 2018/9/15.
*/
public class Tester {
/*
* @param args
*/
public static void main(String[] args) {
Plate plate = new Plate(20);
Thread t1 = new Thread(new Consumer(plate));
t1.setName("CousumerThread-" + 1);
Thread t2 = new Thread(new Producer(plate));
t2.setName("ProducerThread-" + 2);
t1.start();
t2.start();
}
单消费者,单生产者,上面的代码是没有问题的,并且能够很好的运行。
假设我们使用多生产者,多消费者呢?上面的生产者和消费者代码是否有问题?,问题出在哪?
多生产者,多消费者的测试类代码实现如下
package Inter;
/**
* Created by lin on 2018/9/15.
*/
public class Tester {
* @param args
*/
public static void main(String[] args) {
Plate plate = new Plate(20);
Thread t1 = new Thread(new Consumer(plate));
t1.setName("CousumerThread-" + 1);
Thread t3 = new Thread(new Consumer(plate));
t3.setName("CousumerThread-" + 3);
Thread t2 = new Thread(new Producer(plate));
t2.setName("ProducerThread-" + 2);
Thread t4 = new Thread(new Producer(plate));
t4.setName("ProducerThread-" + 4);
t1.start();
t2.start();
t3.start();
}
}
事实上,使用多生产者,多消费者,上述的代码是有问题的,上述中有两个消费者,都调用wait方法等待,假如生产者唤醒t1消费者,t1消费者消费一个物品后plante,调用notify唤醒,由于notify唤醒是随机的,假设此时唤醒的是消费者t3,想像一下会发生什么,此时t3线程继续往下运行!!,而plante为空,t3继续走将发生不可预知的错误,其主要原因就是因为t3在唤醒后不具备继续往后走的条件,应该继续判断是否应该往下执行,故应该将if判断该为while判断改进代码如下
package Inter;
/** 生产者
* Created by lin on 2018/9/15.
*/
public class Consumer implements Runnable {
private Plate plate;
public Consumer(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
synchronized (plate) {
//当容量小于0时,进入等待队列
while (plate.isEmpty()) {
try {
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
plate.getEgg();
plate.notify();
}
}
}
package Inter;
/** 消费者
* Created by lin on 2018/9/15.
*/
public class Producer implements Runnable {
private Plate plate;
public Producer(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
synchronized (plate) {
while (plate.isFull()) {
try {
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object egg = new Object();
plate.addEgg(egg);
plate.notify();
}
}
}
话外音:在判断线程的等待条件时,应该选用while包裹,而不是if包裹
初看一下上面的代码貌似能够很好的执行,应该没问题,其实还是有问题的,问题就出在notify,
package Inter;
/**
* Created by lin on 2018/9/15.
*/
public class Tester {
/**
* 当容量满的时候调用wait方法进入等待队列
* 当竞争锁失败进入同步队列
*
* 假设生产者调用wait方法进入等待队列,唤醒等待队列中的消费者
* 消费过后,plante为空,调用wait方法唤醒等待队列中的消费者,消费
* 者判断plante为空,调用wait方法,进入等待状态,从而造成线程
* 无限等待,没有线程会去通知其他线程苏醒,所有线程全部进入的等待区
*
* @param args
*/
public static void main(String[] args) {
Plate plate = new Plate(20);
Thread t1 = new Thread(new Consumer(plate));
t1.setName("CousumerThread-" + 1);
Thread t3 = new Thread(new Consumer(plate));
t3.setName("CousumerThread-" + 3);
Thread t2 = new Thread(new Producer(plate));
t2.setName("ProducerThread-" + 2);
Thread t4 = new Thread(new Producer(plate));
t4.setName("ProducerThread-" + 4);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
造成上面的根本原因就是因为调用notify()方法只会随机选择在等待队列中的线程随机选择一个进入同步区,如果唤醒的线程再次进入等待区,就导致所有线程都在等待区从而不能苏醒,正确的做法是,使用notifyAll()代替notify(),将等待区的所有线程都进入同步区,从而避免上述场景发生,虽然使用notifyAll()会唤醒不必要的生产者,消耗性能,但这是非常值得的。
话外音:在没有确定的把握时,请不要用notify()代替notifyAll(),即使notifyAll(),虽然notifyAll()会做一些无畏的唤醒操作,但能保证程序的正确运行。
上面已近虽然解决的多生产者,多消费者的问题,但是在JDK1.5后出现了ReentantLock锁,使用其conditon能够唤醒一类线程
从而解决notifyAll()的问题。为了更好的区分取出来的类,我们额外添加一个egg类,给每个egg类唯一标识,示例代码如下:
//新添加的egg类 package Inter.lock; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** * Created by lin on 2018/9/16. */ public class Egg { private final long id; private static AtomicLong sequence = new AtomicLong(0); public Egg() { this.id = sequence.getAndIncrement(); } @Override public String toString() { return "egg" + this.id; } }
//共享区
package Inter.lock; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by lin on 2018/9/16. */ public class PlateLock { private List<Egg> eggs = new ArrayList<>(); private int capacity; private Lock lock = new ReentrantLock(); //等待条件,当队列为空 private Condition empty = lock.newCondition(); //conditon,条件,当队里满了 private Condition Full = lock.newCondition(); public PlateLock(int capacity) { if (capacity <= 0) { throw new IllegalArgumentException("容量不能小于0"); } this.capacity = capacity; } public Object getEgg() { lock.lock(); Object egg = null; try { //队里为空,等待 while (isEmpty()) { empty.await(); } egg = eggs.get(0); System.out.println("消费者取得蛋"+egg); eggs.remove(0); Full.signalAll();//此时队列不满,进行唤醒操作 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return egg; } public void addEgg() { lock.lock(); try { //队里已满 while (isFull()) { Full.await(); //等待 } Egg egg = new Egg(); eggs.add(egg); System.out.println("生产者生产蛋" + egg); empty.signalAll();//生产鸡蛋后,此时队里不为空,进行唤醒 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public int getEggNum() { return eggs.size(); } public boolean isFull() { return this.eggs.size() >= this.capacity; } public boolean isEmpty() { return this.eggs.isEmpty(); } }
//消费者
package Inter.lock; import Inter.Plate; /** * Created by lin on 2018/9/16. */ public class ConsumerLock implements Runnable { private PlateLock plateLock; public ConsumerLock(PlateLock plateLock) { this.plateLock = plateLock; } @Override public void run() { while (true) { plateLock.getEgg(); } } }
//生产者
package Inter.lock; /** * Created by lin on 2018/9/16. */ public class ProducerLock implements Runnable { private PlateLock plateLock; public ProducerLock(PlateLock plateLock) { this.plateLock = plateLock; } @Override public void run() { while (true) { plateLock.addEgg(); } } }
//测试类如下
package Inter.lock; import Inter.Consumer; import Inter.Plate; import Inter.Producer; /** * Created by lin on 2018/9/15. */ public class TesterLock { public static void main(String[] args) { PlateLock plateLock = new PlateLock(20); Thread t1 = new Thread(new ConsumerLock(plateLock)); t1.setName("CousumerThread-" + 1); Thread t3 = new Thread(new ConsumerLock(plateLock)); t3.setName("CousumerThread-" + 3); Thread t2 = new Thread(new ProducerLock(plateLock)); t2.setName("ProducerThread-" + 2); Thread t4 = new Thread(new ProducerLock(plateLock)); t4.setName("ProducerThread-" + 4); t1.start(); t2.start(); t3.start(); } }
。