【问题标题】:Why we must use "while" for checking race condition not "if"为什么我们必须使用“while”来检查竞争条件而不是“if”
【发布时间】:2016-01-16 03:20:36
【问题描述】:

我在“Thinking in java”中阅读了以下代码。

synchronized(obj)
    {
        while (condition_not_matched)
        {
            obj.wait();
        }
        //continue
        dosomething();

    }

我的想法:
使用“if”就可以了,因为“wait”意味着它必须得到obj的锁监视器,并且这里只能执行一个线程。

(1)为什么这里使用“while(条件)”而不是“if”?
(2)执行“obj.wait()”时发生了什么?当前线程是否释放“obj”的锁?
(3)当另一个线程执行“obj.notify()”时,前一个线程发生了什么(它是否重新获取了obj的锁?如果是,它必须condition_not_matched,所以“if”就足够了。)
我错了吗?

【问题讨论】:

  • 一切都可以通过阅读wait()和notify()的javadoc(还有notifyAll(),应该是首选)来回答。
  • 我不会说notifyAll“应该是首选”。有合适的情况,也有不合适的情况。
  • @Wyzard,notifyAll() 应该首选的一种情况是,如果您向不完全了解自己在做什么的新手提供一口大小的建议。如果在可以使用 notify() 的情况下使用 notifyAll(),唯一的后果是程序的性能不如它可能做的那样好。如果你在应该使用 notifyAll() 的地方使用 notify(),那么后果可能比这更糟。

标签: java multithreading while-loop wait


【解决方案1】:

使用 if 检查而不是在循环中重复检查是错误的。使用循环有多种原因。

一个是“虚假唤醒”,这意味着等待方法可以在没有通知线程的情况下返回:根据退出等待方法的线程推断它必须得到通知是无效的。这种情况可能不会经常发生,但这是必须处理的可能性。

但主要原因是这个:当您的线程等待时,它会释放锁。当它收到通知时,它没有锁,必须再次获取它才能退出等待方法。 仅仅因为线程得到通知并不意味着它是下一个获得锁的。如果线程根据当线程没有锁的所有权时发生的事情决定要做什么,在通知发生和线程获得锁的时间之间,多个线程可能有机会对同一个共享对象进行操作,并且共享对象的状态可能不是您的线程认为的那样。使用 while 循环允许线程再次检查它正在等待的条件,同时保持锁,在继续之前确认条件仍然有效。

【讨论】:

  • 虽然有很多解释,但你解释的水平确实有助于理解这个谜题。为此 +1。
【解决方案2】:

wait 方法的 Javadoc 中解释了循环的必要性:

线程也可以在没有被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但应用程序必须通过测试本应导致线程被唤醒的条件,如果条件不满足则继续等待。

为了防止这种情况,在wait()调用返回后,你必须再次检查条件,如果它为假,返回并再次调用wait()而不是继续。 while 循环实现了这一点。


当你调用wait()时,对象的锁在等待的同时自动释放,然后在方法返回之前再次获取。这意味着当另一个线程在对象上调用notify()时,等待线程不能立即恢复运行,因为通知线程仍然持有对象的锁,等待线程必须等待它被释放。这也意味着如果有多个等待线程并且您调用notifyAll(),则等待线程不能一次全部恢复:其中一个线程将获得锁并从wait()返回,当它释放锁时,然后另一个线程可以获取它并从wait()返回,依此类推。

在某些情况下,当涉及多个等待线程时,一个等待线程可能会被唤醒,发现条件为真,然后做一些事情,最终将条件改回为假——所有这些都是在持有锁的情况下进行的。然后,当它释放锁时(例如再次调用wait()),下一个线程唤醒并发现条件为假。在这种情况下,这不是虚假唤醒。条件确实为真,但在线程有机会检查它之前再次变为假。

例如:生产者线程将多个项目添加到队列并调用notifyAll() 来唤醒消费者线程。每个消费者线程从队列中取出一个项目,然后在处理该项目时释放锁。但是如果消费者线程的数量多于添加到队列中的项目,一些线程会醒来发现队列是空的,所以他们只需要重新等待。

检查while 循环中的条件除了处理虚假唤醒之外,还可以处理这种情况。

【讨论】:

    【解决方案3】:

    if 语句通过运行一次来检查表达式是真还是假,然后只有当它为真时才运行语句中的代码。

    在哪里

    while 条件继续执行 while 语句中的代码,直到表达式为真。此外,当您不知道可能需要循环多少次条件时,更适合使用 while 循环。

    obj.wait() - 在这种情况下,导致当前线程等待,直到另一个线程为相应对象调用 notify() 方法或 nofityAll() 方法。如果超时作为参数传递,则胎面将等待一定时间。

    obj.notify() 将唤醒一个正在相应对象监视器上等待的单个线程。只有在当前线程放弃对对象的锁定后,被唤醒的线程才会继续。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-08
      • 2015-10-07
      • 1970-01-01
      • 2013-01-27
      • 1970-01-01
      相关资源
      最近更新 更多