在学习Java 9功能时,我遇到了一种新的Thread
类方法,称为onSpinWait
。根据javadocs,此方法用于此:
指示调用方暂时无法继续,直到 其他活动发生一个或多个操作。
有人可以帮助我理解这种方法,给出一个现实生活中的例子或场景吗?
它与x86操作码相同(并且可能编译为)PAUSE
,等效于Win32宏YieldProcessor
,GCC的__mm_pause()
和C#方法Thread.SpinWait
这是一种非常弱化的屈服形式:它告诉你的 CPU 你处于一个循环中,可能会消耗许多 CPU 周期,等待某些事情发生(忙碌等待)。
这样,CPU 可以将更多资源分配给其他线程,而无需实际加载操作系统调度程序并取消排队准备运行的线程(这可能很昂贵)。
一个常见的用途是旋转锁定,当您知道共享内存上的争用非常罕见或很快完成时,旋转锁可能比普通锁表现得更好。
此类伪代码可能如下所示:
int state = 0; //1 - locked, 0 - unlocked
routine lock:
while state.cas(new_value=1, wanted_value=0) == false //if state is 0 (unlocked), store 1 (locked) and return true, otherwise just return false.
yield
routine unlock:
atomic_store(state,0)
yield
可以用Thread.onSpinWait()
来实现,暗示在尝试锁定锁的同时,CPU可以为其他线程提供更多资源。
这种屈服技术在实现无锁算法时非常普遍和流行,因为它们中的大多数都依赖于繁忙等待(几乎总是作为原子比较和交换循环实现)。 这有你能想象到的每一个现实世界的用途。
纯系统提示!
阅读这篇文章,我引用:
目标
定义一个 API,允许 Java 代码向运行时系统提示它处于旋转循环中。API 将是一个纯粹的提示,并且不会携带任何语义行为要求(例如,no-op 是有效的实现)。允许 JVM 从在某些硬件平台上可能有用的自旋循环特定行为中受益。在 JDK 中提供无操作实现和内部实现,并在至少一个主要硬件平台上展示执行优势。
很多时候,线程必须挂起,直到超出其作用域的内容发生更改。一种(曾经)常见的做法是wait() notify()
模式,其中一个线程等待另一个线程唤醒它们。
这有很大的限制,即另一个线程必须意识到可能存在等待线程并应通知。如果另一个线程的工作超出您的控制范围,则无法收到通知。
唯一的方法是旋转等待。 假设您有一个程序来检查新电子邮件并通知用户:
while(true) {
while(!newEmailArrived()) {
}
makeNotification();
}
这段代码每秒将执行数百万次;一遍又一遍地旋转,使用宝贵的电力和CPU功率。执行此操作的常见方法是在每次迭代中等待几秒钟。
while(true) {
while(!newEmailArrived()) {
try {
Thread.sleep(5000);
} catch(InterruptedException e) {
}
}
makeNotification();
}
这做得很好。但是,在您必须立即工作的情况下,睡眠可能是毫无疑问的。
Java 9试图通过引入这种新方法来解决这个问题:
while(true) {
while(!newEmailArrived()) {
Thread.onSpinWait();
}
makeNotification();
}
这将与没有方法调用时完全相同,但系统可以自由地降低进程优先级;当其他更重要的事情需要资源时,减慢循环或减少此循环上的电力。
举一个真实的例子, 假设你想实现异步日志记录,其中想要记录某些内容的线程不想等待他们的日志消息被"发布"(比如写入文件),只要它最终会这样做(因为他们有真正的工作要做。
Producer(s):
concurrentQueue.push("Log my message")
假设,您决定拥有一个专用的使用者线程,该线程全权负责将日志消息实际写入文件:
(Single)Consumer
while (concurrentQueue.isEmpty())
{
//what should I do?
}
writeToFile(concurrentQueue.popHead());
//loop
问题是在 while 块内部做什么? Java还没有提供理想的解决方案:你可以做一个Thread.sleep(),但能做多久,这是重量级的; 或 Thread.yield(),但这是未指定的,或者您可以使用锁或互斥锁*,但这通常太重量级,也会减慢生产者的速度(并破坏异步日志记录的既定目的)。
你真正想对运行时说,"我预计我不会等待太久,但我想尽量减少等待的任何开销/对其他线程的负面影响"。 这就是 Thread.onSpinWait() 的用武之地。
正如上面的响应所示,在支持它的平台上(如 x86),onSpinWait() 被嵌入到暂停指令中,这将给你带来你想要的好处。 所以:
(Single)Consumer
while (concurrentQueue.isEmpty())
{
Thread.onSpinWait();
}
writeToFile(concurrentQueue.popHead());
//loop
经验表明,这可以改善"忙等待"样式循环的延迟。
我还想澄清一下,它不仅在实现"自旋锁"时有用(尽管在这种情况下它绝对有用);上面的代码不需要任何类型的锁(旋转或其他)。
如果你想进入杂草,你不能比英特尔的规格做得更好
*为了清楚起见,JVM在尝试最小化互斥锁的成本方面非常聪明,并且最初将使用轻量级锁,但这是另一个讨论。
我只想在阅读文档及其源代码后添加我的 2 美分。此方法可能会触发一些优化,并且可能不会 - 因此必须小心 - 您不能真正依赖它 - 因为这对 CPU 的提示大于需求,可能没有任何初始依赖。这意味着它的实际源代码如下所示:
@HotSpotIntrinsicCandidate
public static void onSpinWait() {}
这实际上意味着,根据这一点,这种方法基本上是一个 NO-OP,直到它命中JIT 中的 c2 编译器。