Thread 的 onSpinWait () 方法 class - Java 9

onSpinWait​() method of Thread class - Java 9

在学习 Java 9 个特征时,我遇到了 Thread class 的新方法,称为 onSpinWait​。根据 javadocs,此方法用于此:

Indicates that the caller is momentarily unable to progress, until the occurrence of one or more actions on the part of other activities.

有人可以通过现实生活中的例子或场景帮助我理解这种方法吗?

纯系统提示!

阅读this article我引用:

Goals

Define an API that would allow Java code to hint to the run-time system that it is in a spin loop. The API will be a pure hint, and will carry no semantic behaviour requirements (for example, a no-op is a valid implementation). Allow the JVM to benefit from spin loop specific behaviours that may be useful on certain hardware platforms. Provide both a no-op implementation and an intrinsic implementation in the JDK, and demonstrate an execution benefit on at least one major hardware platform.

很多时候一个线程必须被挂起,直到它的范围之外的东西发生变化。一个(曾经)常见的做法是 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();
}

这将与没有方法调用的情况完全相同,但系统可以自由降低进程优先级;当其他更重要的事情需要其资源时,减慢循环或减少此循环的电力。

它与 x86 操作码 PAUSE 相同(并且可能编译为)并且等效于 Win32 宏 YieldProcessor、GCC 的 __mm_pause() 和 C# 方法 Thread.SpinWait

这是一种非常弱化的屈服形式:它告诉你的 CPU 你正处于一个循环中,这个循环可能会消耗许多 CPU 循环等待某事发生(忙等待)。

这样,CPU 可以将更多资源分配给其他线程,而无需实际加载 OS 调度程序并使准备就绪的 运行 线程出队(这可能很昂贵).

自旋锁的一个常见用途是,当您知道共享内存上的争用很少见或很快完成时,自旋锁可能比普通锁表现更好。

此类的伪代码如下所示:

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可以给其他线程更多的资源。

这种让步技术在实现无锁算法时非常普遍和流行,因为它们中的大多数都依赖于忙等待(几乎总是作为原子比较和交换循环实现)。这具有您可以想象的所有实际用途。

我只是想在阅读文档及其源代码后加我的 2 美分。此方法 可能 触发一些优化,也可能不会 - 所以必须小心 - 你不能真正依赖它 - 因为这是 hintCPU不仅仅是需求,可能没有任何初始依赖......这意味着它的实际源代码如下所示:

@HotSpotIntrinsicCandidate
public static void onSpinWait() {}

根据 this

,这实际上意味着此方法基本上是 NO-OP,直到它到达 JIT 中的 c2 compiler

举一个现实世界的例子, 假设您想实现异步日志记录,其中想要记录某些内容的线程不想等待他们的日志消息获得 "published" (比如写入文件),只要它最终完成(因为他们有真正的工作要做。)

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(),但这是未指定的,或者您可以使用锁或互斥*,但这通常过于重量级并且也会减慢生产者的速度(并且破坏了异步日志记录的既定目的)。

你真正想要的是对运行时说,"I anticipate that I won't be waiting too long, but I'd like to minimize any overhead in waiting/negative effects on other threads"。 这就是 Thread.onSpinWait() 的用武之地。

正如上面的响应所示,在支持它的平台(如 x86)上,onSpinWait() 被内化为 PAUSE 指令,这将为您提供所需的好处。所以:

(Single)Consumer

while (concurrentQueue.isEmpty())
{
    Thread.onSpinWait();

}
writeToFile(concurrentQueue.popHead());
//loop

shown empirically 这可以改善 "busy-waiting" 样式循环的延迟。

我还想澄清一下,它不仅对实施 "spin-locks" 有用(尽管在这种情况下肯定有用);上面的代码不需要任何类型的锁(自旋或其他)。

要想入得了杂草,莫过于Intel's specs

*为清楚起见,JVM 在尝试最小化互斥锁的成本方面非常聪明,并且最初将使用轻量级锁,但那是另一个讨论。