目录
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
注:本文参考生产者消费者模式
适用情景
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。
概念和原理
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。
设计要点
- 生产者只在仓库未满时进行i生产,仓库满时生产者线程被阻塞
- 消费者只在仓库非空时进行消费,仓库为空时消费者线程被阻塞
- 当消费者发现仓库为空时会通知生产者生产
- 当生产者发现仓库满时会通知消费者消费
阻塞队列方式实现
-
实体类(生产和消费的产品)
package com.zjl.thread;
public class Data {
private final int intData;
public Data(int intData) {
this.intData = intData;
}
public Data(String str) {
this.intData = Integer.parseInt(str);
}
public int getIntData() {
return intData;
}
@Override
public String toString() {
return "Data [intData=" + intData + "]";
}
}
- 生产者
package com.zjl.thread;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Producer implements Runnable{
private volatile boolean isRunning = true;
private BlockingQueue<Data> queue;
private static AtomicInteger count = new AtomicInteger();
private static final int SLEEPTIME = 1000;
public Producer(BlockingQueue<Data> queue) {
this.queue = queue;
}
@Override
public void run() {
Data data = null;
Random random = new Random();
System.out.println("start producting id:" + Thread.currentThread().getId());
try {
while (isRunning) {
Thread.sleep(random.nextInt(SLEEPTIME));
data = new Data(count.incrementAndGet());
System.out.println(data + "加入队列。");
if (!queue.offer(data, 2, TimeUnit.SECONDS)) {
System.out.println("加入队列失败!");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
public void stop() {
isRunning = false;
}
}
- 消费者
package com.zjl.thread;
import java.text.MessageFormat;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
public class Consumer implements Runnable {
private BlockingQueue<Data> queue;
private static final int SLEEPTIME = 1000;
public Consumer(BlockingQueue<Data> queue) {
this.queue = queue;
}
@Override
public void run() {
System.out.println("start consumer id:" + Thread.currentThread().getId());
Random random = new Random();
try {
while (true) {
Data data = queue.take();
if (data != null) {
int re = data.getIntData() * data.getIntData();
System.out.println(MessageFormat.format("{0}*{1}={2}", data.getIntData(), data.getIntData(), re));
Thread.sleep(random.nextInt(SLEEPTIME));
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
- 测试类
package com.zjl.thread;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
public class QueueTest {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Data> queue = new LinkedBlockingDeque<>(10);
Producer p1 = new Producer(queue);
Producer p2 = new Producer(queue);
Producer p3 = new Producer(queue);
Consumer c1 = new Consumer(queue);
Consumer c2 = new Consumer(queue);
Consumer c3 = new Consumer(queue);
ExecutorService service = Executors.newCachedThreadPool();
service.execute(p1);
service.execute(p2);
service.execute(p3);
service.execute(c1);
service.execute(c2);
service.execute(c3);
Thread.sleep(10 * 1000);
p1.stop();
p2.stop();
p3.stop();
Thread.sleep(3000);
service.shutdown();
}
}
- 执行效果如下
start producting id:11
start producting id:12
start producting id:10
start consumer id:15
start consumer id:14
start consumer id:13
Data [intData=1]加入队列。
1*1=1
Data [intData=2]加入队列。
2*2=4
Data [intData=3]加入队列。
3*3=9
Data [intData=4]加入队列。
4*4=16
Data [intData=5]加入队列。
5*5=25
Data [intData=6]加入队列。
6*6=36
Data [intData=7]加入队列。
7*7=49
Data [intData=8]加入队列。
8*8=64
Data [intData=9]加入队列。
9*9=81
Data [intData=10]加入队列。
10*10=100
Data [intData=11]加入队列。
11*11=121
Data [intData=12]加入队列。
12*12=144
Data [intData=13]加入队列。
13*13=169
Data [intData=14]加入队列。
14*14=196
Data [intData=15]加入队列。
15*15=225
总结:
因为 BlockingQueue 是阻塞队列,特点如下:
它的存取可以保证只有一个线程在进行操作
生产者在队列满时会阻塞,并唤醒消费者线程
消费者在队列为空时会阻塞,并唤醒生产者线程
notify()和wait()实现
- 产品类
package com.zjl.concurrent;
public class Data {
private long value;
public Data() {
super();
}
public Data(long value) {
super();
this.value = value;
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
@Override
public String toString() {
return "Data [value=" + value + "]";
}
}
- 生产者
package com.zjl.concurrent;
import java.util.List;
import java.util.Random;
public class Producer implements Runnable{
private List<Data> queue;
private int size;
private static final int SLEEPTIME = 1000;
public Producer(List<Data> queue, int size) {
this.queue = queue;
this.size = size;
}
@Override
public void run() {
try {
Random random = new Random();
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
Data data = new Data(random.nextInt(1000));
System.out.println("生产了产品:" + data);
synchronized (queue) {
if (queue.size() >= size) {
queue.notifyAll();
queue.wait();
} else {
queue.add(data);
}
}
Thread.sleep(random.nextInt(SLEEPTIME));
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
- 消费者
package com.zjl.concurrent;
import java.util.List;
import java.util.Random;
public class Consumer implements Runnable {
private List<Data> queue;
private static final int SLEEPTIME = 1000;
public Consumer(List<Data> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
Random random = new Random();
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
Data data = null;
synchronized (queue) {
if (queue.size() == 0) {
queue.notifyAll();
queue.wait();
} else {
data = queue.remove(0);
System.out.println("消费者消费了:" + data);
}
}
Thread.sleep(random.nextInt(SLEEPTIME));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 测试类
package com.zjl.concurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class QueueTest {
public static void main(String[] args) throws InterruptedException {
List<Data> queue = new ArrayList<>();
Producer p1 = new Producer(queue, 10);
Producer p2 = new Producer(queue, 10);
Consumer c1 = new Consumer(queue);
Consumer c2 = new Consumer(queue);
ExecutorService service = Executors.newCachedThreadPool();
service.execute(p1);
service.execute(p2);
service.execute(c1);
service.execute(c2);
}
}
- 运行效果
生产了产品:Data [value=554]
生产了产品:Data [value=149]
生产了产品:Data [value=499]
生产了产品:Data [value=713]
消费者消费了:Data [value=554]
生产了产品:Data [value=447]
生产了产品:Data [value=106]
生产了产品:Data [value=463]
消费者消费了:Data [value=149]
消费者消费了:Data [value=499]
生产了产品:Data [value=568]
生产了产品:Data [value=336]
生产了产品:Data [value=32]
消费者消费了:Data [value=713]
生产了产品:Data [value=786]
消费者消费了:Data [value=447]
生产了产品:Data [value=254]
生产了产品:Data [value=697]
生产了产品:Data [value=78]
消费者消费了:Data [value=106]
消费者消费了:Data [value=463]
消费者消费了:Data [value=568]
生产了产品:Data [value=638]
生产了产品:Data [value=412]
消费者消费了:Data [value=336]
消费者消费了:Data [value=32]
生产了产品:Data [value=296]
生产了产品:Data [value=905]
生产了产品:Data [value=199]
生产了产品:Data [value=365]
总结:
- 添加进集合操作用synchronized块来实现:
集合满容量时:唤醒其他线程,释放锁。
不满时直接添加进集合。
- 消费时,删除操作也要用synchronized块来实现:
集合为空时:唤醒其他线程,释放锁。
不空时直接删除。