前言
本篇详细讲解生产者消费者模式的几种实现方式。
- 使用object.wait()/object.notify()方式实现
- condition.await()/signal()方式实现
- BlockingQueue阻塞队列的方式实现
使用object.wait()/object.notify()方式实现
下面我们直接上代码,通过一个案例来展示。
public class ConsumeTest2 {
private static final ArrayList<String> list = new ArrayList<>(5);
public static void main(String[] args) {
new Thread(new ConsumerImpl(), "consume thread").start();
new Thread(new ProductImpl(), "product thread").start();
}
static class ConsumerImpl implements Runnable {
@Override
public void run() {
while (true) {
synchronized (list) {
if (list.size() == 0) {
try {
System.out.println("size = 0, consume wait");
list.wait();
System.out.println("consume wait finish");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String removeName = list.remove(0);
System.out.println("消费了:" + removeName + "--剩余产品数量:" + list.size());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.notify();
}
}
}
}
static class ProductImpl implements Runnable {
@Override
public void run() {
while (true) {
synchronized (list) {
if (list.size() == 5) {
try {
System.out.println("size = 5, product wait");
list.wait();
System.out.println("product wait finish");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String name = "product:" + (list.size() + 1);
list.add(name);
System.out.println("添加了产品:" + name + "--剩余空间:" + (5 - list.size()));
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.notify();
}
}
}
}
}
//打印信息
size = 0, consume wait
添加了产品:product:1--剩余空间:4
添加了产品:product:2--剩余空间:3
添加了产品:product:3--剩余空间:2
添加了产品:product:4--剩余空间:1
添加了产品:product:5--剩余空间:0
size = 5, product wait
consume wait finish
消费了:product:1--剩余产品数量:4
消费了:product:2--剩余产品数量:3
消费了:product:3--剩余产品数量:2
消费了:product:4--剩余产品数量:1
消费了:product:5--剩余产品数量:0
size = 0, consume wait
- 我们使用object.wait()/notify()实现生产者消费者模式,需要自己控制生产和消费的条件,并且自己控制锁的释放。源码请点击Forward查看。
- object.wait和object.notify方法要成对出现,notify方法只会随机唤醒一个线程,而notifyAll()会唤醒所有的线程。
- object的wait方法会释放锁,但是只能等到此线程执行完毕后才能释放锁。想要调用wait或者notify以及notifyAll方法,线程必须得持有锁对象才可以,关于线程的详细图解可以查看Java线程六种状态图解;
condition.await()/signal()方式实现
public class ConsumeTest3 {
private static final ArrayList<String> list = new ArrayList<>(5);
private static Lock lock = new ReentrantLock();
private static Condition product = lock.newCondition();
private static Condition consume = lock.newCondition();
public static void main(String[] args) {
new Thread(new ConsumerImpl(), "consume thread").start();
new Thread(new ProductImpl(), "product thread").start();
}
static class ConsumerImpl implements Runnable {
@Override
public void run() {
while (true) {
lock.lock();
try {
if (list.size() == 0) {
try {
System.out.println("size = 0, consume wait");
consume.await();
System.out.println("consume wait finish");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String removeName = list.remove(0);
System.out.println("消费了:" + removeName + "--剩余产品数量:" + list.size());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.signal();
} finally {
lock.unlock();
}
}
}
}
static class ProductImpl implements Runnable {
@Override
public void run() {
while (true) {
lock.lock();
try {
if (list.size() == 5) {
try {
System.out.println("size = 5, product wait");
product.await();
System.out.println("product wait finish");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String name = "product:" + (list.size() + 1);
list.add(name);
System.out.println("添加了产品:" + name + "--剩余空间:" + (5 - list.size()));
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
consume.signal();
} finally {
lock.unlock();
}
}
}
}
}
- Condition的await和signal以及signalAll()和Object的wait(),notify(),nofityAll()一一对应,在使用上Condition的加锁方式是显示的调用lock.lock和lock.unlock(),而synchronized加锁和解锁的方式是虚拟机实现的,本质上一样,不过我们不能在代码中看到罢了。
BlockingQueue阻塞队列的方式实现
public class ConsumeTest4 {
private static final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {
new Thread(new ConsumerImpl(), "consume thread").start();
new Thread(new ProductImpl(), "product thread").start();
}
static class ConsumerImpl implements Runnable {
@Override
public void run() {
while (true) {
try {
String removeName = queue.take();
System.out.println("消费了:" + removeName + "--剩余产品数量:" + queue.size());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class ProductImpl implements Runnable {
@Override
public void run() {
while (true) {
try {
String name = "product:" + (queue.size() + 1);
queue.put(name);
System.out.println("添加了产品:" + name + "--剩余空间:" + (5 - queue.size()));
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
- 使用阻塞队列的方式很简单,是因为内部已经处理了关于锁和队列的问题,我们可以在源码中一窥究竟:
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { /** Main lock guarding all access */ final ReentrantLock lock; /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull; public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } public void put(E e) throws InterruptedException { Objects.requireNonNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); enqueue(e); } finally { lock.unlock(); } } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } }
- 由上面源码可知,
ArrayBlockingQueue
内部的实现方式正是我们第二种的方式,所以在本质上他们是一致的。
- 由上面源码可知,