并发编程-生产者消费者模式Java代码实现
生产者消费者模式
-
生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据。
-
消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据。
消费队列可以用来平衡生产和消费的线程资源。
代码如下:
- 使用双向链表和Synchronized锁来实现消息队列。
- 使用Excutors中的创建线程池的方法模拟生产者和消费者线程。
其他可见代码中的注释。
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class MessageTest {
public static void main(String[] args) {
// 创建生产者线程池,总共3个线程,并且传入自定义的线程工厂,这样可以方便给线程起名
ExecutorService producer = Executors.newFixedThreadPool(3, new ThreadFactory() {
private AtomicInteger t = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "生产者-" + t.getAndIncrement());
}
});
// 创建消费者线程池,总共3个线程,并且传入自定义的线程工厂,这样可以方便给线程起名
ExecutorService consumer = Executors.newFixedThreadPool(2, new ThreadFactory() {
private AtomicInteger t = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "消费者-" + t.getAndIncrement());
}
});
// 新建消息队列,队列容量为2
MessageQueue<Message> queue = new MessageQueue<>(2);
// 生产者产生消息
for (int i = 0; i < 5; i++) {
int id = i;
producer.submit(()->{
queue.put(new Message(id, "value : " + id));
});
}
// 消费者消费消息
for (;;) {
consumer.submit(()->{
Message take = queue.take();
});
}
}
}
class MessageQueue<T>{
/*
消息队列的定义,使用LinkedList作为存储消息的数据结构,这样可以将生产的消息直接放入
队列尾部,消费消息时从头部直接获取。消息的类型定义为泛型。
*/
//存储消息的链表
private LinkedList<T> list = new LinkedList<>();
// 消息队列容量
private int capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
}
public T take(){
// 消费者线程取数据, 因为设计到多线程的对共享变量的访问,所以需要加锁,此处也可以使用ReentrantLock加锁
// 此处对变量list加锁,也可以直接对this加锁
synchronized (list){
// 获得当前访问线程的名称
String t_name = Thread.currentThread().getName();
// 判断消息队列是否为空,为空则阻塞,生产者线程放入数据后唤醒
while(list.isEmpty()){
System.out.println("["+t_name+"]"+" 队列为空,消费者线程等待...");
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 不为空则取消息
T message = list.removeFirst();
// 打印操作信息
System.out.println("["+t_name+"]"+" 消费消息: " + message.toString());
// 唤醒阻塞中的线程
list.notifyAll();
return message;
}
}
public void put(T message){
// 生产者线程放入数据, 因为设计到多线程的对共享变量的访问,所以需要加锁,此处也可以使用ReentrantLock加锁
// 此处对变量list加锁,也可以直接对this加锁
synchronized (list){
// 获得当前访问线程的名称
String t_name = Thread.currentThread().getName();
// 判断消息队列是否已满,满则阻塞,消费者线程消费数据后唤醒
while(list.size()==capacity){
System.out.println("["+t_name+"]"+" 队列已满,生产者线程等待...");
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 不满则放入消息
list.addLast(message);
// 打印操作信息
System.out.println("["+t_name+"]"+" 生产消息: " + message.toString());
// 唤醒阻塞中的线程
list.notifyAll();
}
}
}
class Message{
// 消息id
private int id;
// 消息的值
private String value;
public Message(int id, String value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", value='" + value + '\'' +
'}';
}
}
运行结果:
可以看到首先消费者线程消费,但是队列为空,则线程阻塞等待,之后生产者-1和生产者-3生产消息,但是因为队列容量为2,当生产者-2想要继续放入消息时,被阻塞。之后消费者-2进行消费消息之后,生产者-2被唤醒又可以将消息放入到消息队列中去…