Java 生产者-消费者模式实现

Java 生产者-消费者模式实现

模式概述

生产者-消费者模式是经典多线程设计模式,用于协调生产者(生成数据)和消费者(处理数据)的协作。核心目标是通过共享缓冲区传递数据,避免数据竞争和资源浪费,实现高效并发。

核心组件

  • 生产者:生成数据并写入共享缓冲区。
  • 消费者:从共享缓冲区读取数据并处理。
  • 共享缓冲区:存储数据的中间容器(通常为队列),有固定容量限制。
  • 同步机制:确保缓冲区操作线程安全,常用锁+条件变量、阻塞队列等。

实现方式一:Lock + Condition(自定义缓冲区)

通过 ReentrantLock 保证缓冲区操作的原子性,结合两个 ConditionnotEmptynotFull)实现线程间精准通信,解决缓冲区空/满时的线程等待问题。

实现逻辑

  • 缓冲区为先进先出(FIFO)队列,容量固定。
  • 生产者写入数据时,若缓冲区满则等待 notFull 条件(消费者取数据后唤醒)。
  • 消费者读取数据时,若缓冲区空则等待 notEmpty 条件(生产者写数据后唤醒)。

代码示例

package com.deadlock;

import java.nio.file.Path;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Jing61
 */
public class QueueTask {
    private static Buffer buffer = new Buffer();

    /**
     * 存储的工作实例
     */
    private static class Task {
        private String id;

        public Task() {
            // 生成随机ID
            this.id = UUID.randomUUID().toString();
        }

        @Override
        public String toString() {
            return "Task [id=" + id + "]";
        }
    }
    /**
     * 缓冲区实际上是一个队列,用于存放任务
     */
    private static class Buffer {
        private static final int CAPACITY = 10;
        private Queue<Task> queue = new LinkedList<>();
        private Lock lock = new ReentrantLock(true); // 等待时间越久的线程越先被唤醒
        private Condition notFull = lock.newCondition();
        private Condition notEmpty = lock.newCondition();

        /**
         * 生产者
         * @param task 任务
         */
        public void write (Task task) {
            lock.lock();
            try {
                while(queue.size() == CAPACITY) {
                    System.out.println("缓冲区已满,等待 notFUll");
                    notFull.await();
                }
                // 队列未满,将任务写入队列
                queue.offer(task);
                System.out.println("写入任务:" + task.toString());
                // 队列不为空,唤醒 notEmpty 发生
                notEmpty.signalAll();
            } catch (InterruptedException e) {
                System.out.println("等待被中断");
            } finally {
                lock.unlock();
            }
        }

        /**
         * 消费者
         * @return 缓冲区中的任务
         */
        public Task read () {
            Task task = null;
            lock.lock();
            try {
                while(queue.isEmpty()) {
                    System.out.println("缓冲区已空,等待 notEmpty");
                    notEmpty.await();
                }
                // 队列非空,从队列中取出任务
                task = queue.poll();
                System.out.println("读取任务:" + task.toString());
                // 队列非空,唤醒 notFull 发生
                notFull.signalAll();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
            return task;
        }
    }

    /**
     * 生产者
     */
    private static class Producer implements Runnable {

        @Override
        public void run() {
            while(true) {
                Task task = new Task();
                buffer.write(task);
                try {
                    Thread.sleep(500); // 生产者耗时
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 消费者
     */
    private static class Consumer implements Runnable {
        @Override
        public void run() {
            while(true) {
                Task task = buffer.read();
                try {
                    Thread.sleep(700); // 消费者耗时
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public static void main(String[] args) {
        var pool = Executors.newFixedThreadPool(2);
        pool.execute(new Producer());
        pool.execute(new Consumer());
        pool.shutdown();
    }
}

实现方式二:BlockingQueue(阻塞队列,推荐)

Java 并发包提供 BlockingQueue 接口,其实现类(如 ArrayBlockingQueueLinkedBlockingQueue)内置阻塞机制,无需手动处理锁和条件变量,简化开发。

阻塞队列核心特性

  • 当队列满时,put() 方法会阻塞生产者线程,直到队列有空闲空间。
  • 当队列空时,take() 方法会阻塞消费者线程,直到队列有数据。
  • 内置线程安全机制,无需额外同步。

常用实现类

  • ArrayBlockingQueue:基于数组的有界阻塞队列,容量固定。
  • LinkedBlockingQueue:基于链表的阻塞队列,默认无界(可指定容量)。
  • PriorityBlockingQueue:基于优先级的无界阻塞队列,元素按优先级排序。

代码示例

package com.deadlock;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * java集合框架提供了ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue来支持阻塞队列
 * 阻塞队列:试图向满队列添加元素或者从空队列删除元素会导致线程阻塞
 * @author Jing61
 */
public class ProducerConsumerUsingBlockingQueue {
    private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
    
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.execute(new Producer());
        executor.execute(new Consumer());
        executor.shutdown();
    }
    
    private static class Producer implements Runnable{
        @Override
        public void run() {
            try {
                int value = 1;
                while(true) {
                    // offer 添加数据, 添加成功返回true,添加失败返回false,但不阻塞
                    // add 添加数据, 添加成功返回true,添加失败抛出异常,不阻塞
                    // put 添加数据, 无返回值,添加失败阻塞
                    System.out.println("生产者生产数据:" + value);
                    queue.put(value++);
                    Thread.sleep((long)(Math.random() * 5000));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    private static class Consumer implements Runnable{
        @Override
        public void run() {
            try {
                while(true) {
                    // poll 获取数据, 获取成功返回数据,获取失败返回null,不阻塞
                    // take 获取数据, 获取成功返回数据,获取失败阻塞
                    System.out.println("消费者消费数据:" + queue.take());
                    Thread.sleep((long)(Math.random() * 200));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

阻塞队列关键方法对比

方法 队列满时行为 队列空时行为 适用场景
add(E e) 抛出异常 添加成功 需快速失败的场景
offer(E e) 返回 false 添加成功 不希望阻塞的场景
put(E e) 阻塞线程 添加成功 生产者-消费者模式
take() 成功返回数据 阻塞线程 生产者-消费者模式
poll() 成功返回数据 返回null 不希望阻塞的场景

两种实现方式对比

实现方式 优点 缺点
Lock + Condition 灵活性高,可自定义条件逻辑 代码复杂,需手动管理锁和条件
BlockingQueue 代码简洁,无需手动同步 灵活性较低,依赖队列实现
posted @ 2025-11-13 16:56  Jing61  阅读(29)  评论(0)    收藏  举报