生产者消费者模型是并发编程中最经典的问题之一,它描述了多线程环境下生产者和消费者之间的协作关系。在这个模型中,生产者负责生成数据并放入共享缓冲区,而消费者则从缓冲区取出数据进行处理。这种模式在现实中有大量应用场景,比如电商系统中的订单处理、物流系统中的包裹分拣、大数据处理中的数据管道等。
我第一次接触这个模型是在开发一个日志分析系统时。当时系统需要实时处理来自多个服务器的日志数据,处理线程经常因为等待数据而阻塞,或者因为数据堆积导致内存溢出。后来采用生产者消费者模式重构后,系统稳定性得到了显著提升。
共享缓冲区是这个模型的核心,它连接着生产者和消费者。在Java中,我们通常使用以下几种方式实现:
java复制class BoundedBuffer {
final Object[] items = new Object[100];
int putptr, takeptr, count;
public synchronized void put(Object x) throws InterruptedException {
while (count == items.length)
wait();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notifyAll();
}
public synchronized Object take() throws InterruptedException {
while (count == 0)
wait();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notifyAll();
return x;
}
}
重要提示:在真实项目中,除非有特殊需求,否则建议直接使用Java提供的BlockingQueue实现,而不是自己从头实现。这样可以避免很多潜在的线程安全问题。
Java提供了多种同步机制来实现生产者消费者模型:
java复制class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
// 其他代码类似,但使用await()/signal()代替wait()/notify()
}
这种方式的优势在于:
这是最简单也是最推荐的生产者消费者实现方式:
java复制import java.util.concurrent.*;
public class ProducerConsumerDemo {
private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
static class Producer implements Runnable {
public void run() {
try {
int value = 0;
while (true) {
queue.put(value);
System.out.println("Produced: " + value);
value++;
Thread.sleep((long)(Math.random() * 1000));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
static class Consumer implements Runnable {
public void run() {
try {
while (true) {
Integer value = queue.take();
System.out.println("Consumed: " + value);
Thread.sleep((long)(Math.random() * 2000));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(new Producer());
executor.submit(new Consumer());
executor.submit(new Consumer());
executor.shutdown();
}
}
这个实现展示了:
对于更复杂的场景,我们可以考虑以下扩展:
java复制BlockingQueue<PriorityTask> queue = new PriorityBlockingQueue<>(10,
Comparator.comparingInt(PriorityTask::getPriority));
java复制class DelayedTask implements Delayed {
private final long executeTime;
private final String taskName;
public DelayedTask(String name, long delayMs) {
this.taskName = name;
this.executeTime = System.currentTimeMillis() + delayMs;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.executeTime, ((DelayedTask)o).executeTime);
}
}
BlockingQueue<DelayedTask> queue = new DelayQueue<>();
java复制ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 3; i++) {
executor.submit(new Producer());
}
for (int i = 0; i < 7; i++) {
executor.submit(new Consumer());
}
java复制// 生产者批量put
List<Integer> batch = new ArrayList<>(BATCH_SIZE);
// 填充batch
queue.drainTo(batch, BATCH_SIZE);
// 消费者批量take
List<Integer> items = new ArrayList<>(BATCH_SIZE);
queue.drainTo(items, BATCH_SIZE);
processBatch(items);
实战经验:在分布式系统中,可以考虑使用Kafka等消息队列代替内存队列,获得更好的可靠性和扩展性。但对于单JVM应用,内存队列通常性能更好。
Java 8及后续版本提供了一些新的特性可以简化生产者消费者模型的实现:
java复制CompletableFuture.supplyAsync(() -> produce(), producerPool)
.thenAcceptAsync(value -> consume(value), consumerPool);
java复制SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
publisher.subscribe(new Subscriber<>() {
// 实现Subscriber接口方法
});
// 生产者
IntStream.range(0, 100).forEach(publisher::submit);
java复制List<Integer> data = IntStream.range(0, 1000).boxed().collect(Collectors.toList());
data.parallelStream().forEach(this::process);
这些新特性提供了更高级的抽象,但在使用时需要注意:
在实际项目中,我通常会根据场景复杂度选择实现方式。对于简单场景,BlockingQueue就足够了;对于复杂的数据流水线,可能需要考虑响应式编程框架如Reactor或RxJava。