当前位置: 首页 > 知识库问答 >
问题:

在 Java 中使用“同步”代码块和“.wait”和“.notify”

柳刚豪
2023-03-14

我正在学习同步的代码块和wait()/。notify()方法,我很难理解它们在生产者-消费者设置中如何交互。将下面类的同一实例传递给两个线程;一个线程运行生产者方法,另一个线程则运行消费者方法。


    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();
            }

        }
    }

}

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

> < li>

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

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

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

共有2个答案

苏丰茂
2023-03-14

您正在看到线程匮乏的示例。

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

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

问题是,线程在释放< code>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());
}

在这里,我将< code>println(...)从同步块中调用。(我还重命名了您的< code>lock变量,以强调它的具体目的是保护队列。)

通过将<code>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
         .                               .
         .                               .
         .                               .
阎智
2023-03-14

请检查通知的整个留档并等待

 类似资料:
  • 主要内容:1 什么是Java同步代码块,2 Java同步代码块的要点,3 Java同步代码块的语法,4 Java同步代码块的例子1,5 Java同步代码块的例子21 什么是Java同步代码块 同步代码块可用于对方法的任何特定资源执行同步。 假设您的方法中有50行代码,但是您只想同步5行,则可以使用synchronized代码块。 如果将方法的所有代码放在同步代码块中,它的效果与同步方法相同。 2 Java同步代码块的要点 同步代码块用于锁定任何共享资源的对象。 同步代码块的范围小于该方法。 3 

  • Java中,任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。 wait(), notify()和 notifyAll()这些方法在同步代码块中调用 有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thr

  • 问题内容: 我正在尝试检查java中的wait / notify如何工作。 码: 输出返回 我期望在执行notify()时,等待将结束并被打印。但似乎只有完成打印后才能打印。 问题答案: 对象监视器锁需要执行相同锁的单个引用。 在你的榜样,你是对的一个实例,但使用从。相反,您应该使用单个通用锁定对象…例如 输出… 并可能根据线程调度更改位置。 您可以尝试将睡眠排除在障碍之外。这将释放监视器锁定,从

  • 问题内容: 我了解到,调用对象的方法将释放对象监视器(如果存在)。 但是我对通过另一个线程调用该对象有一些疑问: (何时)等待线程唤醒,如果同时有另一个(第3个)线程拥有对象监视器? 如果在该对象上调用了第三个线程,等待线程将被唤醒吗? 是否可以确定线程是否正在等待通知特定对象(java 1.4 / java 5) 如果将在方法中调用会发生什么情况? 问题答案: 将唤醒在监视器上等待的一个线程。除

  • 问题内容: 我们正在与节点合作,主要用于内部项目,并了解使用该技术的最佳方法。 并非来自特定的异步背景,学习曲线可能是一个挑战,但是我们已经习惯了框架和学习过程。 使我们两极分化的是,何时才是使用同步代码与异步代码的最佳时间。我们目前使用的规则是,如果任何东西与IO进行交互,那么它必须通过回调或事件发射器(即给定的)是异步的,但是可以将任何未使用IO的其他项构造为同步函数(此方法还将取决于函数本身

  • 问题内容: 我可以得到一个完整的简单方案,即建议如何使用它的教程,特别是在队列中吗? 问题答案: 和方法被设计为提供一种机制,以允许一个线程块,直到一个特定的条件被满足。为此,我假设你要编写一个阻塞队列实现,其中具有一些固定大小的元素后备存储。 你要做的第一件事是确定你希望方法等待的条件。在这种情况下,你将希望该方法阻塞直到存储空间可用,并且你将希望该方法阻塞直到返回某些元素。 关于必须使用等待和