繁体   English   中英

在 Java 中使用带有 .wait 和 .notify 的 `synchronized` 代码块

[英]Using `synchronized` code blocks with `.wait` and `.notify` in Java

我正在学习 Java 中的synchronized代码块和.wait() / .notify()方法,并且很难理解它们在生产者-消费者设置中的交互方式。 下面 class 的相同实例传递给两个线程; 一个线程运行生产者方法,另一个线程运行消费者方法。


    private Queue<Integer> queue = new LinkedList<>();
    private Object lock = new Object();

    public void producer() throws InterruptedException {
        Random random = new Random();
        while (true) {

            synchronized(lock) {
                while (queue.size() > 10) {
                    lock.wait();
                }

                queue.add(random.nextInt(100));
                lock.notify();
            }

        }
    }

    public void consumer() throws InterruptedException {
        while (true) {

            synchronized(lock) {
                if (queue.isEmpty()) {
                    lock.wait();
                }

                int val = queue.remove();
                System.out.println(val + ": " + queue.size());
                lock.notify();
            }

        }
    }

}

在这里,在同一个 object 上synchronized使得两个代码块中只有一个同时运行。 假设生产者线程赢得比赛,向队列中添加一个元素,并调用 notify。 此时,消费者线程将在消费者 function 中的synchronized(lock)处等待(由于sycnhornized ,它从未将 go 进入其代码块)。 一旦生产者线程退出其同步代码块,消费者线程将进入其同步代码块。 现在,队列是非空的,因为生产者只是在通知之前放入了一些东西。 消费者线程将删除它,调用通知,退出它的块,此时生产者将获取锁,因为它现在一直在生产者 function 中的synchronized(lock)行等待。 三个问题:

  1. 在我看来,我们在生产者和消费者之间交替,因此队列大小将在 0 和 1 之间波动。我错过了什么?

  2. 既然退出同步代码块释放了等待线程可以看到和获取的锁,为什么我们需要整个等待和通知机制? 在我看来,我上面描述的notify没有做任何事情,因为一旦锁可用,另一个线程就会获取它并进入它的代码块。

  3. lock.notify()是否也会唤醒在synchronized(lock)处等待的线程?

请检查通知等待的整个文档

您正在看到一个线程饥饿的示例。

饥饿发生的一种方法是,如果您编写这样的循环:

while (true) {
    synchronized(lock) {
        ...
    }
}

问题是,线程在释放lock后所做的下一件事就是再次锁定它。 如果任何其他线程当前被阻塞等待同一个锁,那么执行这个循环的线程几乎肯定会赢得再次锁定它的竞赛,因为正在执行循环的线程已经在运行,但是另一个线程需要时间“唤醒”向上。”

在这种情况下,我们说另一个线程“饿死”了。

一些线程库提供了所谓的公平锁,它通过确保总是将锁授予等待时间最长的线程来避免饥饿。 但是公平锁通常不是默认设置,因为它们会损害设计更好的程序的性能,在这些程序中锁的竞争不是那么激烈。


在您的示例中,饥饿并不是一场彻底的灾难,因为每个线程在没有工作要做时都会调用wait() 这会释放锁并允许其他线程运行。 但它几乎迫使线程“轮流”:一个总是在睡觉,而另一个在工作。 你也可以把它写成一个单线程程序。


如果您的线程不保持任何锁定超过绝对必要的时间,那就更好了:

while (true) {
    int val;
    synchronized(queue_lock) {
        if (queue.isEmpty()) {
            lock.wait();
        }

        val = queue.remove();
        queue_lock.notify();
    }
    System.out.println(val + ": " + queue.size());
}

在这里,我将println(...)调用移出同步块。 (我还重命名了您的lock变量,以强调它的目的是保护队列。)

您可以通过将random()调用移出同步块来在生产者线程中执行相同的操作。 这样你就有更多的机会让两个线程并行运行——生产者可以在生产每一个新事物的同时消费者同时处理它已经“消费”的一些事物。


澄清一下:以下是实际可能发生的情况:

producer                              consumer
---------------------------------     -----------------------------------
                                      enter synchronized block
tries to enter synchronized block     queue.isEmpty() => true
                                      lock.wait()
                                          ...releases the lock...
enters synchronized block                 ...awaiting notification...
queue.add(...)                            ...awaiting notification...
lock.notify()                             ...now awaiting the lock...
leave synchronized block                  ...starts to wake up, but...
enter synchronized block                  ...Dang! Lost the race...
queue.add(...)                            ...awaiting the lock...
lock.notify()
leave synchronized block                  ...starts to wake up, but...
enter synchronized block                  ...Dang! Lost the race...
    .                                     ...awaiting the lock...
    .                                          .
    .                                          .
queueSize() > 10                               .
lock.wait()
    ...releases the lock...               ...starts to wake up, and...
    ...awaiting notification...           ...FINALLY! re-acquire the lock, and...
         .                             lock.wait() returns
         .                             val = queue.remove()
         .                             ...
    ...now awaiting the lock...        lock.notify()
    ...starts to wake up, but...       leave synchronized block
    ...Dang! Lost the race...          enter synchronized block
         .                               .
         .                               .
         .                               .

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM