在多线程编程中,生产者消费者模型是一个经典的设计模式,它解决了不同线程间工作速率不匹配的问题。想象一下餐厅后厨的场景:厨师(生产者)不断制作菜品,服务员(消费者)持续将菜品端给顾客。如果厨师做菜太快而服务员来不及送,菜品就会堆积;反之服务员则会闲置等待。这个模型的核心就是通过一个缓冲区(好比传菜窗口)来平衡两者的工作节奏。
在Java中实现这个模型,主要涉及三个关键组件:
这个模型在现实开发中应用广泛,比如:
这是最传统的实现方式,直接使用Object类的wait()和notify()方法:
java复制class Buffer {
private Queue<Integer> queue = new LinkedList<>();
private int maxSize = 10;
public synchronized void produce(int value) throws InterruptedException {
while (queue.size() == maxSize) {
wait(); // 缓冲区满时等待
}
queue.add(value);
notifyAll(); // 唤醒可能等待的消费者
}
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 缓冲区空时等待
}
int value = queue.poll();
notifyAll(); // 唤醒可能等待的生产者
return value;
}
}
关键点:必须使用while循环检查条件,而不是if语句。这是因为存在"虚假唤醒"的可能性,线程可能在没有收到notify的情况下被唤醒。
Java 5引入的java.util.concurrent.locks包提供了更灵活的锁机制:
java复制class Buffer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
// 其余代码与上例类似
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == maxSize) {
notFull.await();
}
queue.add(value);
notEmpty.signal();
} finally {
lock.unlock();
}
}
// consume方法类似
}
优势分析:
对于大多数实际场景,直接使用Java内置的BlockingQueue是最佳选择:
java复制BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
// 生产者线程
queue.put(item); // 自动阻塞如果队列满
// 消费者线程
Integer item = queue.take(); // 自动阻塞如果队列空
实测建议:ArrayBlockingQueue在大多数场景下性能最优,LinkedBlockingQueue适合任务大小差异大的情况,PriorityBlockingQueue需要特殊排序时使用。
下面是一个使用BlockingQueue的完整可运行示例:
java复制import java.util.concurrent.*;
public class ProducerConsumerDemo {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
// 生产者
Runnable producer = () -> {
try {
for (int i = 0; i < 10; i++) {
queue.put(i);
System.out.println("Produced: " + i);
Thread.sleep((int)(Math.random() * 1000));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
// 消费者
Runnable consumer = () -> {
try {
for (int i = 0; i < 10; i++) {
Integer value = queue.take();
System.out.println("Consumed: " + value);
Thread.sleep((int)(Math.random() * 1500));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
new Thread(producer).start();
new Thread(consumer).start();
}
}
执行效果分析:
实际场景中更常见的是多对多的关系:
java复制// 创建10个生产者和5个消费者
ExecutorService executor = Executors.newFixedThreadPool(15);
for (int i = 0; i < 10; i++) {
executor.submit(producer);
}
for (int i = 0; i < 5; i++) {
executor.submit(consumer);
}
性能提示:消费者数量通常应少于生产者,具体比例取决于任务处理耗时与任务产生速度的关系。
实际系统需要考虑如何优雅关闭:
java复制volatile boolean shutdown = false;
// 生产者修改
while (!shutdown && i < 10) {
// 生产逻辑
}
// 消费者修改
while (!shutdown || !queue.isEmpty()) {
// 消费逻辑
}
在高负载情况下需要防止内存溢出:
java复制// 使用有界队列
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1000);
// 或者使用拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 5, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时由调用者线程执行
);
典型死锁情况:
解决方案:
常见性能问题:
优化技巧:
java复制// 使用更高效的并发队列
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
// 或者使用无锁实现(高并发场景)
BlockingQueue<Integer> queue = new ConcurrentLinkedQueue<>();
确保消费端正确处理异常:
java复制try {
while (true) {
Item item = queue.take();
try {
process(item);
} catch (Exception e) {
// 记录日志并继续
log.error("Process item failed", e);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
更常见的模式是结合ExecutorService:
java复制ExecutorService producers = Executors.newFixedThreadPool(5);
ExecutorService consumers = Executors.newFixedThreadPool(3);
BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>(100);
// 生产者提交任务
producers.submit(() -> {
taskQueue.put(() -> {
// 实际任务逻辑
});
});
// 消费者执行任务
consumers.submit(() -> {
Runnable task = taskQueue.take();
task.run();
});
对于分布式系统,可以使用:
Java客户端示例(使用Redis):
java复制Jedis jedis = new Jedis("localhost");
// 生产者
jedis.rpush("task-queue", "task-data");
// 消费者
while (true) {
List<String> task = jedis.blpop(0, "task-queue");
process(task.get(1));
}
更精细的流量控制:
java复制Semaphore semaphore = new Semaphore(10); // 最大10个并发
Runnable task = () -> {
semaphore.acquire();
try {
// 处理任务
} finally {
semaphore.release();
}
};
java复制// 定期打印队列状态
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
System.out.println("Queue size: " + queue.size());
}, 0, 1, TimeUnit.SECONDS);
java复制// 注册MBean
ManagementFactory.getPlatformMBeanServer().registerMBean(
new QueueMonitor(queue),
new ObjectName("com.example:type=QueueMonitor")
);
// QueueMonitor实现
public class QueueMonitor implements QueueMonitorMBean {
private final BlockingQueue<?> queue;
public QueueMonitor(BlockingQueue<?> queue) {
this.queue = queue;
}
@Override
public int getQueueSize() {
return queue.size();
}
}
测试不同实现的吞吐量(ops/ms):
| 实现方式 | 1生产者1消费者 | 5生产者3消费者 |
|---|---|---|
| wait/notify | 1,200 | 3,800 |
| Lock/Condition | 1,500 | 4,500 |
| ArrayBlockingQueue | 2,000 | 6,000 |
| LinkedBlockingQueue | 1,800 | 5,500 |
| Disruptor(第三方库) | 15,000 | 50,000 |
性能提示:对于超高并发场景,可以考虑Disruptor等专门优化的库。
最佳缓冲区大小公式(经验值):
code复制buffer_size = (produce_time / consume_time) * consumer_count * 2
例如:
最佳实践配置:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(), // 核心线程数
Runtime.getRuntime().availableProcessors() * 2, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new LinkedBlockingQueue<>(1000), // 工作队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
java复制interface Consumer {
void onMessage(Message msg);
}
class Producer {
private List<Consumer> consumers = new CopyOnWriteArrayList<>();
public void addConsumer(Consumer consumer) {
consumers.add(consumer);
}
public void produce(Message msg) {
consumers.forEach(c -> c.onMessage(msg));
}
}
java复制class ProcessingChain {
private BlockingQueue<Item> queue;
private List<Processor> processors;
public void start() {
new Thread(() -> {
while (true) {
Item item = queue.take();
for (Processor p : processors) {
item = p.process(item);
}
}
}).start();
}
}
java复制class MonitoringQueue implements BlockingQueue {
private final BlockingQueue delegate;
private final Counter counter;
// 实现所有方法,委托给delegate
public boolean offer(E e) {
counter.increment();
return delegate.offer(e);
}
}
java复制@Test
public void testProducerConsumer() throws InterruptedException {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
AtomicInteger consumed = new AtomicInteger();
// 启动消费者
new Thread(() -> {
while (true) {
queue.take();
consumed.incrementAndGet();
}
}).start();
// 生产者生产100个元素
for (int i = 0; i < 100; i++) {
queue.put(i);
}
// 验证
Thread.sleep(1000); // 给消费者时间处理
assertTrue(consumed.get() >= 90); // 允许少量未完成
}
java复制@StressTest(threads = 50)
public void stressTest() {
// 50个线程并发生产和消费
// 持续运行5分钟
// 检查最终数据一致性
}
需要特别测试的场景:
java复制@Configuration
@EnableAsync
public class AppConfig {
@Bean
public BlockingQueue<Message> messageQueue() {
return new LinkedBlockingQueue<>(1000);
}
@Bean
public TaskExecutor producerExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
return executor;
}
}
@Component
public class MessageProducer {
@Async("producerExecutor")
public void produce(Message msg) {
messageQueue.put(msg);
}
}
java复制// 使用SLF4J记录关键事件
private static final Logger logger = LoggerFactory.getLogger(Producer.class);
public void run() {
try {
while (true) {
Item item = queue.take();
logger.debug("Processing item: {}", item.getId());
// 处理逻辑
}
} catch (InterruptedException e) {
logger.info("Consumer interrupted", e);
Thread.currentThread().interrupt();
}
}
java复制// 使用Micrometer记录指标
Metrics.gauge("queue.size", queue, Collection::size);
// 记录处理时间
Timer timer = Metrics.timer("processing.time");
timer.record(() -> {
// 处理逻辑
});
手动实现时常见的错误:
java复制// 错误示例 - 缺少notify
public synchronized void produce(int value) {
while (queue.size() == maxSize) {
wait();
}
queue.add(value);
// 忘记调用notify()
}
java复制// 错误示例 - 使用if而不是while
public synchronized int consume() {
if (queue.isEmpty()) { // 应该用while
wait();
}
// ...
}
java复制// 错误示例 - 可能无法释放锁
public synchronized void produce(int value) {
if (queue.size() == maxSize) {
throw new RuntimeException("Queue full"); // 异常导致锁无法释放
}
queue.add(value);
notifyAll();
}
正确做法:
java复制public void produce(int value) {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
除了BlockingQueue,Java还提供了其他线程安全集合:
适合键值对存储:
java复制ConcurrentMap<String, Item> map = new ConcurrentHashMap<>();
map.compute("key", (k, v) -> v == null ? new Item() : v.update());
适合读多写少的列表:
java复制List<Listener> listeners = new CopyOnWriteArrayList<>();
listeners.add(listener); // 写时复制
无界非阻塞队列:
java复制Queue<Item> queue = new ConcurrentLinkedQueue<>();
queue.offer(item); // 不使用阻塞操作
确保标志位的可见性:
java复制private volatile boolean running = true;
public void shutdown() {
running = false; // 对所有线程立即可见
}
安全发布对象:
java复制class Config {
private final Map<String, String> settings;
public Config(Map<String, String> settings) {
this.settings = Collections.unmodifiableMap(new HashMap<>(settings));
}
}
确保操作顺序:
java复制// 写操作
sharedVariable = 1;
lock.unlock(); // 释放锁建立happens-before关系
// 读操作
lock.lock();
int value = sharedVariable; // 保证能看到之前写入的值
java复制CompletableFuture.supplyAsync(() -> {
// 生产者逻辑
return result;
}).thenAcceptAsync(result -> {
// 消费者逻辑
}, consumerExecutor);
使用Project Reactor:
java复制Flux.generate(sink -> {
Item item = queue.take();
sink.next(item);
})
.subscribe(item -> {
// 处理item
});
kotlin复制fun main() = runBlocking {
val channel = Channel<Int>(capacity = 10)
// 生产者
launch {
for (x in 1..10) {
channel.send(x)
}
}
// 消费者
launch {
for (y in channel) {
println("Received $y")
}
}
}
java复制void shutdown() {
shutdown = true; // 标志位
producerExecutor.shutdown();
consumerExecutor.shutdown();
try {
if (!producerExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
producerExecutor.shutdownNow();
}
if (!consumerExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
consumerExecutor.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
java复制List<Runnable> remainingTasks = executor.shutdownNow();
if (!remainingTasks.isEmpty()) {
logger.warn("{} tasks were not executed", remainingTasks.size());
// 可以选择持久化未处理任务
}
java复制Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (!queue.isEmpty()) {
logger.error("Queue not empty on shutdown: {} items", queue.size());
}
}));
go复制ch := make(chan int, 10) // 缓冲channel
// 生产者
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()
// 消费者
for item := range ch {
fmt.Println(item)
}
python复制from queue import Queue
from threading import Thread
q = Queue(maxsize=10)
def producer():
for i in range(10):
q.put(i)
def consumer():
while True:
item = q.get()
# 处理item
q.task_done()
Thread(target=producer).start()
Thread(target=consumer).start()
q.join() # 等待所有任务完成
javascript复制// Node.js使用worker_threads
const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
// 主线程作为生产者
const worker = new Worker(__filename);
worker.postMessage('task data');
} else {
// 工作线程作为消费者
parentPort.on('message', (msg) => {
// 处理消息
});
}
优化前:
java复制public synchronized void addItem(Item item) {
// 整个方法同步
}
优化后:
java复制private final Object lock = new Object();
public void addItem(Item item) {
// 非同步预处理
synchronized (lock) {
// 最小化同步块
}
// 非同步后处理
}
java复制// 消费者一次处理多个项目
List<Item> batch = new ArrayList<>(BATCH_SIZE);
queue.drainTo(batch, BATCH_SIZE); // 批量获取
if (!batch.isEmpty()) {
processBatch(batch);
}
对于高并发场景,考虑伪共享问题:
java复制// 使用@Contended注解(Java 8+)
@Contended
class Counter {
private volatile long value;
}
选择依据:
锁的公平性设置:
java复制// 公平锁(降低吞吐量但减少线程饥饿)
ReentrantLock lock = new ReentrantLock(true);
权衡点:
经过多年实践,我认为以下几点最为关键:
优先使用BlockingQueue:除非有特殊需求,否则应该首选Java内置的并发集合,它们经过充分测试和优化。
合理设置队列容量:根据实际生产消费速率比设置缓冲区大小,太大浪费内存,太小影响吞吐量。
正确处理中断:所有阻塞操作都应该正确处理InterruptedException,通常的做法是恢复中断状态:
java复制try {
queue.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 清理资源后退出
}
监控必不可少:生产环境必须监控队列积压情况,可以设置阈值报警。
考虑使用现有框架:对于复杂场景,考虑使用Akka、Disruptor等专业框架,而非重复造轮子。
测试要全面:特别要测试边界条件和高并发场景,包括:
文档记录设计决策:特别是关于队列大小、线程数量等参数的选取依据,方便后续调优。