如何使用notify
和notifyall
解决饥饿?
如果我们有4个线程,等待获取同一个obj的锁,并且当前线程调用notify()
JVM将选择任何一个线程。是否有可能JVM再次选择调用notify()
的线程,因为在调用notify()
之后,它也将在等待线程列表中。
如果其中一个线程被调用的次数多于其他线程,这可能会导致饥饿问题,除非有某种机制首先拾取最长等待线程。
我在这里假设所有线程都具有相同的优先级。如果线程优先级不同,我认为在notify()
此外,同样的问题会出现notifyall()
我认为我们不知道哪个线程会被挑选。
答案 0 :(得分:5)
如果您关心哪个线程得到通知,那么您做错了什么。无论通知需要做什么,任何等待通知的线程都必须才能完成。如果此逻辑不适用于您的用例,则notify
不适合您的用例。
对线程公平性的关注通常表示设计不良的代码。这是你的工作,确保你的代码只在你真正希望它做的工作,并在必要时,首先做最重要的工作。您不应该期望调度程序以某种方式执行此操作。调度程序的工作是尽可能多地完成工作,强制执行流程之间的优先级和公平性。程序员的工作是编写能够正常工作的代码。
答案 1 :(得分:3)
基本上,线程是从notify()
函数或notifiAll()
函数中随机选取的。
您可以做的是您可以使用ReetrantLock
公平政策。公平政策避免线索饥饿。
private final ReentrantLock lock = new ReentrantLock(true);
答案 2 :(得分: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) ;
}
然后到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未指示应通知线程的顺序。但是,如果没有其他人的话,我就学到了一些东西。
答案 3 :(得分:1)
使用wait / notify(All)解决这个问题并不容易,但是你可以看看ReentrantLock / Condition的公平性设置为true。 ReentrantLock javadoc。不过你的哪个线程能够完成工作并不重要。
答案 4 :(得分:0)
不,调用Object.notify
的线程必须拥有该对象的锁,并通过调用Object.wait
通知之前将其锁定在该对象上的线程通知 - 通知线程无法同时通知并等待。
来自Object.notify
JavaDoc:
唤醒的线程将无法继续,直到当前线程放弃对此对象的锁定。唤醒的线程将以通常的方式与可能主动竞争同步此对象的任何其他线程竞争;例如,被唤醒的线程在下一个锁定此对象的线程中没有可靠的特权或劣势