“饥饿”是指一种特定形式的争用,其中一个(或几个)特定线程比其他线程更频繁地被锁定。当线程以某种方式交互时会发生这种情况,以至于每次“饥饿”线程尝试获取某个锁时,它总是在某个其他线程已经锁定它之后的片刻。
一个常见的新手错误,您可以在此站点上找到许多示例,如下所示:
while (trivialTest()) {
synchronized (lock) {
processThatTakesSomeTime();
}
}
这里的问题是,线程释放后几乎接下来要做的事情就是lock
再次锁定它。任何其他想要锁定同一个锁的线程都将有效地“进入睡眠状态”,等待第一个线程完成processThatTakesSomeTime()
调用。
当第一个线程解锁锁时,这是一场看哪个线程可以再次锁定它的竞赛,但是第一个线程具有巨大的优势,因为它已经在运行,而其他线程可能已经被操作系统“换出”,总是输掉比赛。
解决“新手错误”的一种方法是使用所谓的“乐观锁定”:
while (trivialTest()) {
Snapshot snapshot;
boolean success=false;
while (! success) {
synchronized (lock) {
snapshot = takeSnapshotOfSharedData();
}
Result result = doSomeComputationWith(snapshot);
synchronized (lock) {
if (itStillMakesSenseToPublishResult(snapshot, result)) {
publishToSharedData(result);
success = true;
}
}
}
}
这是“乐观的”,因为我们希望内循环每次都能成功。但有时情况并非如此,因为其他一些线程会以与我们的result
. 在这种情况下,我们必须把工作扔掉,然后再试一次。
这似乎违反直觉,但仅将锁保持锁定足够长的时间以拍摄快照或验证和发布结果所获得的性能可能比偶尔不得不放弃工作并重试所损失的性能要大得多。