一、前言
阅读文章的过程中如果碰到不懂的,可以留言或私信我,我会抽空回答。
编程过程中,我们所有遇到的多线程问题,都可以抽象为三种模型:
- 生产者与消费者;
- 读者与写者;
- 哲学家吃饭问题。
能搞清楚这三种模型的实现方式与解决方案,在实际编程中碰到多线程问题处理起来才能得心应手。
二、生产者与消费者
1. 什么是生产者消费者?
生产者与消费者描述的是这样一个多线程问题:有一个或多个生产者生产出某种物品,然后加入到某个容器中,有一个或多个消费者,会不断的从容器中取走物品。当容器满了的时候,生产者会暂时停止生产,当容器为空的时候,消费者会停止消费。
2. 生产者与消费者的线程安全问题
生产者往容器里面加东西,消费者从容器取出东西,多个线程(生产者,消费者)同时对一个对象(容器)进行操作,必然会带来线程安全问题。生产者与消费者模型,最主要的就是如何保证线程安全。
3. 一次简单的尝试
根据上面对生产者和消费者的描述,我们可以用代码给它表达出来:
public class FailProducerConsumer {
public static void main(String[] args) {
List<Integer> queue = new ArrayList<>();
new Thread(new Producer(queue)).start();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
class Producer implements Runnable {
private final List<Integer> queue;
Producer(List<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
Integer value = 1;
while (true) {
queue.add(value++);
}
}
}
class Consumer implements Runnable {
private final List<Integer> queue;
Consumer(List<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
if (!queue.isEmpty()) {
queue.remove(0);
}
}
}
}
上面这段代码存在一个问题:多个Producer
和多个Consumer
同时对queue
进行操作,而queue
并不是线程安全的,这会导致结果并不符合预期。
4. 简易版生产者消费者
上面代码的问题是由于queue
同时被多个线程操作导致的,解决这个问题最简单的方法就是给queue
加锁,让同一时间只能有一个线程操作queue
:
public class SimpleProducerConsumer {
private final List<Integer> buffer = new ArrayList<>();
public static void main(String[] args) {
BufferOperator bufferOperator = new BufferOperator(new Buffer(new ArrayList<>()));
new Thread(new Producer(bufferOperator)).start();
new Thread(new Consumer(bufferOperator)).start();
}
}
class Producer implements Runnable {
private final BufferOperator bufferOperator;
public Producer(BufferOperator bufferOperator) {
this.bufferOperator = bufferOperator;
}
@Override
public void run() {
int i = 0;
while (true) {
try {
bufferOperator.offer(i++);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
private final BufferOperator bufferOperator;
public Consumer(BufferOperator bufferOperator) {
this.bufferOperator = bufferOperator;
}
@Override
public void run() {
while (true) {
try {
bufferOperator.poll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Buffer {
private final List<Integer> buffer;
public Buffer(List<Integer> buffer) {
this.buffer = buffer;
}
public void add(Integer object) {
this.buffer.add(object);
}
public Integer removeFirst() {
return this.buffer.remove(0);
}
public boolean isEmpty() {
return buffer.isEmpty();
}
}
class BufferOperator {
private final Buffer buffer;
public BufferOperator(Buffer buffer) {
this.buffer = buffer;
}
public void offer(Integer object) throws InterruptedException {
synchronized (buffer) {
System.out.println("add object " + object);
buffer.add(object);
}
Thread.sleep(500);
}
public void poll() throws InterruptedException {
synchronized (buffer) {
if (!buffer.isEmpty()) {
System.out.println("poll + " + buffer.removeFirst());
} else {
System.out.println("Queue is empty!");
}
}
Thread.sleep(1000);
}
}
小知识点:这段代码对Buffer
的操作放到BufferOperator
中,将操作Buffer
的部分交给BufferOperator
,是为了符合SRP原则。
5. 使用BlockingQueue实现生产者消费者
Java其实已经给我们提供了一种线程安全的队列:BlockingQueue
,常用的有ArrayBlockingQueue
和LinkedBlockingQueue
(有界队列和无界队列)。如果我们使用BlockingQueue
来实现生产者与消费者,则无需担心线程安全问题,会方便很多。代码如下:
public class BQProducerConsumer {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new LinkedBlockingDeque<>();
new Thread(new Consumer(queue)).start();
new Thread(new Producer(queue)).start();
}
}
class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
int i = 0;
while (true) {
System.out.println("offer " + i++);
queue.offer(i);
}
}
}
class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
System.out.println("poll " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6. 使用线程池实现生产者消费者
public class ThreadPoolProducerConsumer {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
// Executors vs ThreadPoolExecutors
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new Producer(queue));
executorService.submit(new Consumer(queue));
}
}
class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
int i = 0;
while (true) {
System.out.println("offer " + i);
queue.offer(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
System.out.println("poll " + queue.take());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}