管程

运用信号量操作可能过于复杂,不利于扩大生产,故而有了管程,可以更加轻松的实现同步和互斥操作。

在Java中,synchronized 关键字和条件变量(通过 Object 类中的 wait(), notify()notifyAll() 方法实现)的组合可以有效地解决同步问题,如经典的生产者-消费者问题。以下是一个使用 synchronized 和条件变量来解决生产者和消费者问题的示例:

生产者和消费者问题

假设有一个共享的缓冲区,生产者向其中添加数据,消费者从中取数据。我们将使用一个固定大小的数组作为缓冲区。生产者在缓冲区满时需要等待,消费者在缓冲区空时需要等待。

public class ProducerConsumerExample {
    private static final int CAPACITY = 5;
    private final int[] buffer = new int[CAPACITY];
    private int front = 0, rear = 0, count = 0;

    public synchronized void produce(int value) throws InterruptedException {
        while (count == CAPACITY) {
            // 缓冲区满,等待消费者消费
            wait();
        }

        buffer[rear] = value;
        rear = (rear + 1) % CAPACITY;
        count++;
        System.out.println("Produced: " + value);

        // 通知可能在等待的消费者
        notifyAll();
    }

    public synchronized int consume() throws InterruptedException {
        while (count == 0) {
            // 缓冲区空,等待生产者生产
            wait();
        }

        int value = buffer[front];
        front = (front + 1) % CAPACITY;
        count--;
        System.out.println("Consumed: " + value);

        // 通知可能在等待的生产者
        notifyAll();
        return value;
    }

    public static void main(String[] args) {
        ProducerConsumerExample example = new ProducerConsumerExample();

        // 创建生产者线程
        Thread producerThread = new Thread(() -> {
            try {
                int value = 0;
                while (true) {
                    example.produce(value++);
                    Thread.sleep(1000); // 模拟耗时的生产过程
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        // 创建消费者线程
        Thread consumerThread = new Thread(() -> {
            try {
                while (true) {
                    example.consume();
                    Thread.sleep(1000); // 模拟耗时的消费过程
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producerThread.start();
        consumerThread.start();
    }
}

代码解释:

  1. 同步方法produce()consume() 方法都被标记为 synchronized,这确保了每次只有一个线程可以执行这些方法。这是必要的,因为它们修改共享数据(缓冲区和相关的计数器)。

  2. 等待和通知:使用 wait() 方法让线程等待条件变为真。当生产者发现缓冲区已满时,它会调用 wait() 并释放锁,从而允许其他线程(如消费者)执行。消费者在缓冲区为空时也会这么做。当生产者放入一个项目或消费者取出一个项目时,它们会调用 notifyAll() 来唤醒所有等待的线程。

  3. 循环缓冲区:使用简单的数组实现循环缓冲区,其中 rearfront 指针帮助在数组中环绕。

这个例子展示了如何使用 synchronized 和条件变量(wait()notifyAll())来处理多线程中的生产者-消费者问题,确保线程安全并有效地使用资源。

为什么不使用`notify()`?

假设一个生产者生产了之后执行该函数,即唤醒一个等待在该对象object的线程,如果这个线程是生产者,那么这个生产者有可能会发生阻塞,从而导致死锁。

设想在唤醒该生产者的前几个时刻,又有多个生产者被阻塞在等待队列上,那么就有可能唤醒其余生产者,而不是消费者。