生产者-消费者模型
要点
- 生产者仅负责产生数据结果,不关心数据如何处理
- 消费者专心处理数据结果
- 消息队列可以用来平衡生产者与消费者的线程资源
- 消息队列有容量限制,满时不会再加入数据
- JDK中各种阻塞队列,采用的就是这种模式
代码示例
消息队列
消息队列是模型中核心组成部分,控制着入队和出队逻辑。
消息队列存在容量上线,当消息队列满时,如果有线程希望继续向队列中添加消息,则会进入等待状态;同理,当消息队列为空时,如果有线程希望继续从队列中取出消息,也会进入等待状态。
因此,再完成向消息队列中存入/取出消息时,应该调用notifyAll()
方法唤醒正在等待的线程。
同时,为了让每个消息都拥有唯一的id,我们专门封装了一个Message
类,并将其设计为final
类型以防止类中的逻辑被覆盖。
class MessageQueue{
private final LinkedList<Message> list= new LinkedList<>();
private int capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
}
// 获取消息
public Message take(){
// 检查队列是否为空
synchronized (list) {
while (list.isEmpty()) {
log.info("队列为空");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = list.removeFirst();
list.notifyAll();
return message;
}
}
// 存入消息
public void put(Message message){
synchronized (list){
while (list.size() >= capacity){
log.info("队列已满");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.addLast(message);
list.notifyAll();
}
}
}
final class Message{
private int id;
private Object value;
public Message(int id, Object value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public Object getValue() {
return value;
}
}
我们采用了一个简单的自增id作为生成器的逻辑,为了防止因为异步而其生成相同的id,我们对核心方法genarate()
进行了同步处理。
final class IdGenerator{
private static int id = 1;
public static synchronized int genarate(){
return id++;
}
}
生产者
生产者的主要工作内容为产生消息,并将消息放入到消息队列中。sleepTime
变量是为了控制生产者的生产频率:随着产生消息次数的增多,其生产频率会逐渐加快。
Runnable producerTask = () -> {
long sleepTime = 2000;
while (true) {
int id = IdGenerator.genarate();
Integer value = random.nextInt(1000);
Message message = new Message(id, value);
log.info("生成消息:id={},value={}", id, value);
queue.put(message);
log.info("成功放入消息:id={}", id);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
sleepTime /= 2;
}
};
消费者
消费者的主要工作内容为从消息队列中拿到消息,并进行处理(这里的工作为打印其内容)。sleepTime
变量是为了控制消费者的消费频率:随着处理消息次数的增多,其消费频率会逐渐减慢。
Runnable consumerTask = () -> {
long sleepTime = 200;
while (true) {
Message message = queue.take();
log.info("获得消息:id={},value={}", message.getId(), message.getValue());
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
sleepTime += 100;
}
};
测试方法
在测试方法中,我们一共创建了5个生产者和3个消费者,它们之间相互独立。
public class ProducerAndConsumer {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue(5);
Random random = new Random();
Runnable producerTask = () -> {
long sleepTime = 2000;
while (true) {
int id = IdGenerator.genarate();
Integer value = random.nextInt(1000);
Message message = new Message(id, value);
log.info("生成消息:id={},value={}", id, value);
queue.put(message);
log.info("成功放入消息:id={}", id);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
sleepTime /= 2;
}
};
Runnable consumerTask = () -> {
long sleepTime = 200;
while (true) {
Message message = queue.take();
log.info("获得消息:id={},value={}", message.getId(), message.getValue());
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
sleepTime += 100;
}
};
// 创建5个生产者
for (int i = 0; i < 5; i++) {
new Thread(producerTask,"生产者-"+i).start();
}
// 创建3个消费者
for (int i = 0; i < 3; i++) {
new Thread(consumerTask,"消费者-"+i).start();
}
}
}
从如下打印结果中能够看出:程序运行初期,消费者的消费速度快于生产者的生产速度,会出现队列为空的情况;程序运行后期,生产者的生产速度逐渐赶超消费者的消费速度,会出现队列已满的情况。
[165 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=3,value=838
[164 ms] [INFO][消费者-0] i.k.e.c.p.MessageQueue : 队列为空
[165 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=2,value=255
[164 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=5,value=820
[164 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=4,value=463
[164 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=1,value=383
[168 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=5
[168 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=4
[168 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=1
[168 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=3,value=838
[168 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=2
[168 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=3
[168 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=5,value=820
[168 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=2,value=255
[370 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=4,value=463
[370 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=1,value=383
[371 ms] [INFO][消费者-0] i.k.e.c.p.MessageQueue : 队列为空
[670 ms] [INFO][消费者-2] i.k.e.c.p.MessageQueue : 队列为空
[670 ms] [INFO][消费者-1] i.k.e.c.p.MessageQueue : 队列为空
[2174 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=8,value=749
[2174 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=7,value=621
[2174 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=9,value=987
[2174 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=6,value=9
[2174 ms] [INFO][消费者-1] i.k.e.c.p.MessageQueue : 队列为空
[2174 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=8,value=749
[2174 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=8
[2174 ms] [INFO][消费者-2] i.k.e.c.p.MessageQueue : 队列为空
[2174 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=10,value=543
[2175 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=10
[2175 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=9
[2174 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=6
[2175 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=7
[2175 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=6,value=9
[2175 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=9,value=987
[2484 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=10,value=543
[2576 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=7,value=621
[2576 ms] [INFO][消费者-2] i.k.e.c.p.MessageQueue : 队列为空
[2892 ms] [INFO][消费者-0] i.k.e.c.p.MessageQueue : 队列为空
[3079 ms] [INFO][消费者-1] i.k.e.c.p.MessageQueue : 队列为空
[3188 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=12,value=524
[3188 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=11,value=436
[3188 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=12,value=524
[3188 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=11
[3188 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=13,value=937
[3188 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=15,value=841
[3188 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=11,value=436
[3188 ms] [INFO][消费者-0] i.k.e.c.p.MessageQueue : 队列为空
[3188 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=14,value=478
[3189 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=12
[3189 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=13
[3189 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=15
[3189 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=14
[3189 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=13,value=937
[3696 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=14,value=478
[3696 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=18,value=891
[3696 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=16,value=120
[3696 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=15,value=841
[3696 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=17,value=686
[3696 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=20,value=27
[3696 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=18
[3696 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=19,value=95
[3696 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=20
[3696 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=16
[3696 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=17
[3696 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=19
[3804 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=18,value=891
[3959 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=24,value=134
[3959 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=21,value=131
[3959 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=24
[3959 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=25,value=811
[3959 ms] [INFO][生产者-1] i.k.e.c.p.MessageQueue : 队列已满
[3959 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=23,value=311
[3959 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=22,value=982
[3959 ms] [INFO][生产者-4] i.k.e.c.p.MessageQueue : 队列已满
[3959 ms] [INFO][生产者-0] i.k.e.c.p.MessageQueue : 队列已满
[3959 ms] [INFO][生产者-3] i.k.e.c.p.MessageQueue : 队列已满
park与unpark
功能与用法
方法park()
与unpark()
都是LockSupport
类中的方法,其功能分别为暂停当前线程的运行与恢复某个线程的运行。
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(某个线程对象);
特点
与Object的wait-notify相比:
wait()
、notify()
和notifyAll()
需要与Monitor一起使用,而park()
、unpark()
不必;park()
、unpark()
是以线程为单位来阻塞和唤醒线程的,而notify()
时随机唤醒,notifyAll()
时全体唤醒,没有那么精确;- 先
unpark()
再park()
线程不会暂停,而先notifyAll()
再wait()
,线程会暂停。
原理
每个线程都有自己的一个 Parker 对象,由三部分组成 :_counter
,_cond
和 _mutex
。
情况1:正常情况下进行park()
- 当前线程调用
Unsafe.park()
方法 - 检查
_counter
,本情况为 0,这时,获得_mutex
互斥锁 - 线程进入
_cond
条件变量(进入后会阻塞) - 设置
_counter = 0
情况2:对阻塞的线程进行unpark()
- 调用
Unsafe.unpark(Thread_0)
方法,设置_counter 为 1
- 唤醒
_cond
条件变量中的 Thread_0 - Thread_0 恢复运行
- 设置
_counter = 0
情况3:对正常线程先进行unpark()
再park()
- 调用
Unsafe.unpark(Thread_0)
方法,设置_counter = 1
- 当前线程调用
Unsafe.park()
方法 - 检查
_counter
,本情况为 1,这时线程无需阻塞,继续运行 - 设置
_counter = 0
注意:从此情况可以分析出,如果进行连续多次unpark()
操作,但也只会生效一次。
线程状态转换
假设有线程t
情况1:NEW ==> RUNNABLE
当调用t.start()
时,NEW ==> RUNNABLE
情况2:RUNNABLE <==> WAITING
线程t使用synchronized(obj)
获取了对象锁后
- 调用
obj.wait()
方法时,RUNNABLE ==> WAITING - 调用
obj.notify()
,obj.notifyAll()
,t.interrupt()
时- 竞争锁成功,线程t从 WAITING ==> RUNNABLE
- 竞争锁失败,线程t从 WAITING ==> BLOCKED
情况3:RUNNABLE <==> WAITING
- 线程t调用其他线程的``join()```方法时,线程t从RUNNABLE ==> WAITING
- 注意:是线程t在其它线程的监视器上等待
- 当其他线程运行结束时,或有额外线程调用了
t.interrupt()
方法,会让线程t从WAITING ==> RUNNABLE
情况4:RUNNABLE <==> WAITING
- 线程t内调用
LockSupport.park()
会让它从RUNNABLE ==> WAITING - 调用
LockSupport.unpark(t)
或有额外线程调用了t.interrupt()
方法,会让线程t从WAITING ==> RUNNABLE
情况5:RUNNABLE <==> TIMED_WAITING
线程t使用synchronized(obj)
获取了对象锁后
- 调用
obj.wait(long n)
方法时,RUNNABLE ==> TIMED_WAITING - 当线程t等待时间超过n毫秒时,或调用
obj.notify()
,obj.notifyAll()
,t.interrupt()
时- 竞争锁成功,线程t从 TIMED_WAITING ==> RUNNABLE
- 竞争锁失败,线程t从 TIMED_WAITING ==> BLOCKED
情况6:RUNNABLE <==> TIMED_WAITING
- 线程t调用其他线程的``join(long n)```方法时,线程t从RUNNABLE ==> TIMED_WAITING
- 注意:是线程t在其它线程的监视器上等待
- 当线程t等待时间超过n毫秒时,当其他线程运行结束时,或有额外线程调用了
t.interrupt()
方法,会让线程t从TIMED_WAITING ==> RUNNABLE
情况4:RUNNABLE <==> TIMED_WAITING
- 线程t内调用
LockSupport.parkNanos(long nanos)
或LockSupport.parkUntil(long millis)
会让它从RUNNABLE ==> TIMED_WAITING - 当线程t等待超时,或有额外线程调用了
LockSupport.unpark(t)
或者t.interrupt()
方法,会让线程t从TIMED_WAITING ==> RUNNABLE
情况8:RUNNABLE <==> TIMED_WAITING
- 线程t内调用
Thread.sleep(long n)
会让它从RUNNABLE ==> TIMED_WAITING - 当线程t等待时间超过n毫秒时,或有额外线程调用了
t.interrupt()
方法,会让线程t从TIMED_WAITING ==> RUNNABLE
情况9:RUNNABLE <==> BLOCKED
- 线程t使用
synchronized(obj)
获取对象锁时,竞争失败了,从RUNNABLE ==> BLOCKED - 持有
obj
锁的线程释放锁时,会唤醒该对象上所有BLOCKED的线程重新竞争,如果线程t竞争成功,会从BLOCKED ==> RUNNABLE ,其它失败的线程仍然为BLOCKED
情况10:RUNNABLE ==> TERMINATED
当线程所有代码运行完毕,会从RUNNABLE ==> TERMINATED