2

如何用notifyand解决饥饿问题notifyall

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

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

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

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

此外,notifyall()我认为在我们不知道将选择哪个线程的地方也会出现同样的问题。

4

5 回答 5

5

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

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

于 2013-06-12T10:45:32.007 回答
3

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

private final ReentrantLock lock = new ReentrantLock(true);
于 2013-06-12T10:46:22.070 回答
3

让我们问一下虚拟机实际上在做什么。我要看看 OpenJDK。经过一番挖掘(这里有源代码),我们发现等待方法是由这段 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) ;
}

然后ObjectMonitor返回到inflate我们发现此表扬的地方

   // 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 没有给出通知线程的顺序。但是如果没有其他人,我学到了一些东西。

于 2013-06-12T13:50:49.293 回答
1

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

于 2013-06-12T10:47:16.813 回答
0

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

来自Object.notifyJavaDoc:

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

于 2013-06-12T10:49:26.880 回答