【问题标题】:How to solve starvation with notify and notifyall?如何用 notify 和 notifyall 解决饥饿问题?
【发布时间】:2013-06-12 10:40:32
【问题描述】:

如何使用notifynotifyall 解决饥饿问题?

如果我们有 4 个线程,等待获取同一个 obj 的锁,并且当前线程调用 notify()

JVM 将选择任何一个线程。调用notify()的线程是否有可能再次被JVM选中,因为调用notify()之后,它也会在等待线程列表中。

如果其中一个线程被调用的次数比其他线程多,这可能会导致饥饿问题,除非有某种机制可以首先选择等待时间最长的线程。

我在这里假设所有线程都具有相同的优先级。如果线程优先级不同,我认为notify()时会选择最高优先级的线程

另外,notifyall() 也会出现同样的问题,我想我们不知道会选择哪个线程。

【问题讨论】:

    标签: java multithreading notify


    【解决方案1】:

    如果您关心通知哪个线程,那么您做错了什么。无论通知需要完成,任何等待通知的线程必须能够完成。如果此逻辑不适用于您的用例,则 notify 不适合您的用例。

    对线程公平性的担忧通常表明代码设计不佳。你的工作是确保你的代码只做你真正希望它做的工作,如果有必要,首先做最重要的工作。您不应该期望调度程序以某种方式执行此操作。调度程序的工作是尽可能多地完成工作,在进程之间强制执行优先级和公平性。程序员的工作是编写能正确工作的代码。

    【讨论】:

    • 依赖公平性来保证正确性的应用程序设计不佳。但是应用程序也可以依靠公平性来(平均)最小化执行时间。
    【解决方案2】:

    基本上,线程是从notify() 函数或notifiAll() 函数中随机挑选的。 你可以做的是你可以使用ReetrantLock with Fair 政策。公平政策避免线程饥饿。

    private final ReentrantLock lock = new ReentrantLock(true);
    

    【讨论】:

    • ReentrantLock 有newCondition 允许您使用公平锁获得等待/通知语义。这个条件是公平的,notify 按 FIFO 顺序唤醒线程。
    【解决方案3】:

    让我们问一下虚拟机实际上在做什么。我要看看 OpenJDK。经过一番挖掘(源代码可用here)我们发现wait方法是由C++的这一点实现的。

    JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
      JVMWrapper("JVM_MonitorWait");
      Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
      assert(obj->is_instance() || obj->is_array(), "JVM_MonitorWait must apply to an object");
      JavaThreadInObjectWaitState jtiows(thread, ms != 0);
      if (JvmtiExport::should_post_monitor_wait()) {
        JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
      }
      ObjectSynchronizer::wait(obj, ms, CHECK);
    JVM_END
    

    我们遵循为ObjectSynchronizer定义的以下内容

    void ObjectSynchronizer::waitUninterruptibly (Handle obj, jlong millis, TRAPS) {
      if (UseBiasedLocking) {
        BiasedLocking::revoke_and_rebias(obj, false, THREAD);
        assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
      }
      if (millis < 0) {
        TEVENT (wait - throw IAX) ;
        THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
      }
      ObjectSynchronizer::inflate(THREAD, obj()) -> wait(millis, false, THREAD) ;
    }
    

    然后到inflate 返回的ObjectMonitor,我们可以在其中找到此推荐

       // Enter the waiting queue, which is a circular doubly linked list in this case
       // but it could be a priority queue or any data structure.
    

    还有一些实现上述双向链表的ObjectWaiter代码。

    那么我学到了什么?嗯,首先,Hotspot 代码实际上并不难导航。但与您的问题更相关 - 在(JVM 的这个实现)中,等待集被实现为一个队列......所以线程在先进先出的基础上被赋予锁定。这意味着如果任何其他线程正在等待锁,他们将首先获得它。

    当然,我们永远不应该使用这些信息……正如上面的评论所说,可以实现等待列表,但是 JVM 人员想要实现它。 JLS 没有给出通知线程的顺序。但是我学到了一些其他人没有的东西。

    【讨论】:

      【解决方案4】:

      使用 wait/notify(All) 解决起来并不容易,但是您可以查看 ReentrantLock/Condition 并将 fairness 设置为 true。 ReentrantLock javadoc。不过,您的哪个线程获得工作并不重要。

      【讨论】:

        【解决方案5】:

        不,调用Object.notify 的线程必须拥有对象上的锁,并通过调用Object.wait 通知先前放弃对该对象的锁的线程 - 通知线程不能同时通知和等待。

        来自Object.notifyJavaDoc:

        在当前线程放弃对该对象的锁定之前,被唤醒的线程将无法继续。被唤醒的线程将以通常的方式与可能正在积极竞争以在此对象上同步的任何其他线程竞争;例如,被唤醒的线程在成为下一个锁定该对象的线程方面没有任何可靠的特权或劣势

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-01-07
          • 2014-07-07
          • 1970-01-01
          • 1970-01-01
          • 2020-02-19
          • 2012-10-11
          • 2019-02-07
          • 1970-01-01
          相关资源
          最近更新 更多