Java版生产者消费者模型,线程间通信

1.这是一个使用 Java 实现的生产者-消费者模型的示例,展示线程间通信。示例1使用BlockingQueue 来简化实现,BlockingQueue底层使用 wait() 和 notify() 机制来协调生产者和消费者线程。

示例1:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ProducerConsumerExample {
    public static void main(String[] args) {
        // 创建一个容量为10的阻塞队列
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);

        // 创建生产者线程
        Thread producerThread = new Thread(new Producer(queue), "Producer");
        // 创建消费者线程
        Thread consumerThread = new Thread(new Consumer(queue), "Consumer");

        // 启动线程
        producerThread.start();
        consumerThread.start();
    }
}

class Producer implements Runnable {
    private final BlockingQueue<Integer> queue;

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + " producing: " + i);
                queue.put(i); // 放入队列,自动阻塞如果队列满
                Thread.sleep(1000); // 模拟生产耗时
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

class Consumer implements Runnable {
    private final BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                Integer item = queue.take(); // 从队列取出,自动阻塞如果队列空
                System.out.println(Thread.currentThread().getName() + " consuming: " + item);
                Thread.sleep(1500); // 模拟消费耗时
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

说明:

  1. BlockingQueue:使用 LinkedBlockingQueue 作为线程安全的队列,容量为10。put()take() 方法会自动处理线程同步,当队列满时生产者阻塞,当队列空时消费者阻塞。
  2. 生产者Producer 线程循环生产1到10的整数,放入队列,每次生产后休眠1秒模拟耗时。
  3. 消费者Consumer 线程从队列中取出数据并消费,每次消费后休眠1.5秒模拟耗时。
  4. 线程通信BlockingQueue 内部使用 wait()notify() 机制实现线程间通信,生产者和消费者通过队列协调工作。
  5. 异常处理:捕获 InterruptedException 并恢复中断状态,确保线程安全退出。

运行结果:

运行程序后,您会看到生产者和消费者交替工作,输出类似:

Producer producing: 1
Consumer consuming: 1
Producer producing: 2
Consumer consuming: 2
...

这个示例展示了生产者-消费者模式的核心思想,适合初学者理解线程间通信。


2. 示例2是使用 wait()notify() 手动实现的生产者-消费者模型,不依赖 BlockingQueue,而是使用自定义的缓冲区和同步机制来实现线程间通信。

示例2:

public class ProducerConsumerManual {
    public static void main(String[] args) {
        Buffer buffer = new Buffer(5); // 缓冲区容量为5

        // 创建生产者线程
        Thread producerThread = new Thread(new Producer(buffer), "Producer");
        // 创建消费者线程
        Thread consumerThread = new Thread(new Consumer(buffer), "Consumer");

        // 启动线程
        producerThread.start();
        consumerThread.start();
    }
}

class Buffer {
    private int[] items;
    private int size;
    private int count;
    private int in; // 生产者放入位置
    private int out; // 消费者取出位置

    public Buffer(int size) {
        this.items = new int[size];
        this.size = size;
        this.count = 0;
        this.in = 0;
        this.out = 0;
    }

    public synchronized void put(int item) throws InterruptedException {
        // 缓冲区满时等待
        while (count == size) {
            System.out.println(Thread.currentThread().getName() + " waiting: buffer full");
            wait();
        }
        // 放入数据
        items[in] = item;
        in = (in + 1) % size;
        count++;
        System.out.println(Thread.currentThread().getName() + " produced: " + item);
        // 通知消费者
        notify();
    }

    public synchronized int take() throws InterruptedException {
        // 缓冲区空时等待
        while (count == 0) {
            System.out.println(Thread.currentThread().getName() + " waiting: buffer empty");
            wait();
        }
        // 取出数据
        int item = items[out];
        out = (out + 1) % size;
        count--;
        System.out.println(Thread.currentThread().getName() + " consumed: " + item);
        // 通知生产者
        notify();
        return item;
    }
}

class Producer implements Runnable {
    private final Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 10; i++) {
                buffer.put(i);
                Thread.sleep(1000); // 模拟生产耗时
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

class Consumer implements Runnable {
    private final Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 10; i++) {
                buffer.take();
                Thread.sleep(1500); // 模拟消费耗时
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

说明:

  1. Buffer 类

    • 使用固定大小的数组(容量为5)作为缓冲区。
    • put()take() 方法使用 synchronized 确保线程安全。
    • 当缓冲区满(count == size)时,生产者调用 wait() 等待;当缓冲区空(count == 0)时,消费者调用 wait() 等待。
    • 生产或消费后,通过 notify() 唤醒等待的线程。
    • 使用循环队列(inout 指针)管理缓冲区位置。
  2. 生产者

    • 生产1到10的整数,放入缓冲区,每次生产后休眠1秒模拟耗时。
  3. 消费者

    • 从缓冲区取出数据并消费,每次消费后休眠1.5秒模拟耗时。
  4. 线程通信

    • 使用 wait()notify() 实现线程间通信,生产者和消费者通过缓冲区的状态(满或空)协调工作。
    • synchronized 确保同一时间只有一个线程访问缓冲区。
  5. 异常处理

    • 捕获 InterruptedException 并恢复中断状态,确保线程安全退出。

运行结果:

运行程序后,您会看到生产者和消费者交替工作,输出类似:

Producer produced: 1
Consumer consumed: 1
Producer produced: 2
Consumer consumed: 2
Producer produced: 3
...
Producer waiting: buffer full
Consumer consumed: 8
Producer produced: 9
...

关键点:

  • 使用 while 循环检查条件(而不是 if),防止虚假唤醒(spurious wakeup)。
  • notify() 只会唤醒一个等待线程,适合单生产者单消费者场景。如果需要支持多生产者多消费者,可以使用 notifyAll()
  • 相比 BlockingQueue,手动实现更清晰地展示了 wait()notify() 的工作原理。

3. 示例3 是使用 wait()notifyAll() 手动实现的多生产者多消费者模式的 Java 示例。这个示例支持多个生产者和消费者线程,通过同步机制协调访问共享缓冲区。使用 notifyAll() 代替 notify(),以确保在多线程场景下所有等待的线程都能被唤醒检查条件。

实例3:

public class MultiProducerConsumer {
    public static void main(String[] args) {
        Buffer buffer = new Buffer(5); // 缓冲区容量为5

        // 创建3个生产者线程
        for (int i = 1; i <= 3; i++) {
            new Thread(new Producer(buffer, i), "Producer-" + i).start();
        }

        // 创建2个消费者线程
        for (int i = 1; i <= 2; i++) {
            new Thread(new Consumer(buffer, i), "Consumer-" + i).start();
        }
    }
}

class Buffer {
    private int[] items;
    private int size;
    private int count;
    private int in; // 生产者放入位置
    private int out; // 消费者取出位置

    public Buffer(int size) {
        this.items = new int[size];
        this.size = size;
        this.count = 0;
        this.in = 0;
        this.out = 0;
    }

    public synchronized void put(int item, String producerName) throws InterruptedException {
        // 缓冲区满时等待
        while (count == size) {
            System.out.println(producerName + " waiting: buffer full");
            wait();
        }
        // 放入数据
        items[in] = item;
        in = (in + 1) % size;
        count++;
        System.out.println(producerName + " produced: " + item);
        // 通知所有等待线程
        notifyAll();
    }

    public synchronized int take(String consumerName) throws InterruptedException {
        // 缓冲区空时等待
        while (count == 0) {
            System.out.println(consumerName + " waiting: buffer empty");
            wait();
        }
        // 取出数据
        int item = items[out];
        out = (out + 1) % size;
        count--;
        System.out.println(consumerName + " consumed: " + item);
        // 通知所有等待线程
        notifyAll();
        return item;
    }
}

class Producer implements Runnable {
    private final Buffer buffer;
    private final int id;

    public Producer(Buffer buffer, int id) {
        this.buffer = buffer;
        this.id = id;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 5; i++) { // 每个生产者生产5个数据
                int item = i * 100 + id; // 生产唯一数据,如101, 102, ..., 501, 502
                buffer.put(item, Thread.currentThread().getName());
                Thread.sleep((long) (Math.random() * 1000)); // 随机休眠0-1秒
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

class Consumer implements Runnable {
    private final Buffer buffer;
    private final int id;

    public Consumer(Buffer buffer, int id) {
        this.buffer = buffer;
        this.id = id;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 8; i++) { // 每个消费者消费8个数据
                buffer.take(Thread.currentThread().getName());
                Thread.sleep((long) (Math.random() * 1500)); // 随机休眠0-1.5秒
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

说明:

  1. Buffer 类

    • 使用固定大小的数组(容量为5)作为循环缓冲区。
    • put()take() 方法使用 synchronized 确保线程安全。
    • 当缓冲区满(count == size)时,生产者调用 wait() 等待;当缓冲区空(count == 0)时,消费者调用 wait() 等待。
    • 使用 notifyAll() 唤醒所有等待线程,适合多生产者多消费者场景,因为 notify() 可能只唤醒同类型线程(如只唤醒另一个生产者),导致死锁。
    • 使用 while 循环检查条件,防止虚假唤醒。
  2. 生产者

    • 创建3个生产者线程,每个生产5个数据(共15个数据)。
    • 每个生产者生成唯一的数据(如 Producer-1 生产 101, 201, ..., 501),便于区分。
    • 生产后随机休眠0-1秒,模拟不同生产速度。
  3. 消费者

    • 创建2个消费者线程,每个消费8个数据(共16个数据,略多于生产数据以确保缓冲区最终被清空)。
    • 消费后随机休眠0-1.5秒,模拟不同消费速度。
  4. 线程通信

    • 使用 wait()notifyAll() 实现线程间通信。notifyAll() 确保所有等待的生产者和消费者线程都能被唤醒,检查缓冲区状态。
    • 同步方法确保同一时间只有一个线程访问缓冲区。
  5. 异常处理

    • 捕获 InterruptedException 并恢复中断状态,确保线程安全退出。

运行结果:

运行程序后,您会看到多个生产者和消费者交替工作,输出类似:

Producer-1 produced: 101
Producer-2 produced: 102
Consumer-1 consumed: 101
Producer-3 produced: 103
Consumer-2 consumed: 102
Producer-1 produced: 201
Producer-2 waiting: buffer full
Consumer-1 consumed: 103
Producer-2 produced: 202
...

关键点:

  • notifyAll() vs notify():在多线程场景下,notifyAll() 确保所有等待线程(生产者和消费者)都被唤醒,避免只唤醒同类型线程导致的死锁。
  • 唯一数据:生产者生成的数据包含线程ID(如101, 102),便于跟踪生产和消费的对应关系。
  • 随机休眠:模拟生产者和消费者处理速度的差异,增加并发场景的真实性。
  • 缓冲区管理:使用循环队列(inout 指针)高效管理缓冲区空间。

注意事项:

  • 程序设计为生产15个数据,消费16个数据,确保所有数据被消费后,消费者可能因缓冲区空而等待。如果需要程序自动终止,可以在 Buffer 类中添加终止条件(如特殊值或标志)。
  • 注意这里还可以进一步扩展,添加日志、性能监控,支持动态生产者/消费者数量 等等。
posted @ 2025-05-25 21:06  gongchengship  阅读(165)  评论(0)    收藏  举报