Thread类的onSpinWait()方法

Jok*_*ker 29 java concurrency multithreading spinlock java-threads

在学习Java 9时,我遇到了一种新的Thread类方法,叫做onSpinWait?.根据javadocs,这个方法用于此:

表示调用者暂时无法进展,直到其他活动发生一个或多个操作为止.

有人能帮助我理解这种方法给出一个真实的例子吗?

Dav*_*aim 20

它与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)
Run Code Online (Sandbox Code Playgroud)

yield可以实现Thread.onSpinWait(),暗示在尝试锁定锁时,CPU可以为其他线程提供更多资源.

PS.这种屈服技术在实现无锁算法时非常普遍和流行,因为它们中的大多数依赖于忙等待(几乎总是作为原子CAS循环实现).这有你可以想象的每一个真实世界的用途.


Mor*_*hai 17

纯系统暗示!

阅读这篇文章我引用:

目标

定义一个API,允许Java代码提示它处于旋转循环中的运行时系统.API将是纯粹的提示,并且不会带有语义行为要求(例如,无操作是有效的实现).允许JVM受益于可能在某些硬件平台上有用的自旋循环特定行为.在JDK中提供无操作实现和内部实现,并在至少一个主要硬件平台上演示执行优势.

有很多次线程必须暂停,直到其范围之外的某些内容发生变化.(曾经)常见的做法是wait() notify()有一个线程等待另一个线程唤醒它们的模式.

这有一个很大的限制,即,另一个线程必须知道可能存在等待线程并且应该通知.如果其他线程的工作超出您的控制范围,则无法获得通知.

唯一的方法是旋转等待.假设您有一个检查新电子邮件的程序并通知用户:

while(true) {
    while(!newEmailArrived()) {
    }
    makeNotification();
}
Run Code Online (Sandbox Code Playgroud)

这段代码每秒会执行数百万次; 使用宝贵的电力和CPU电力一遍又一遍地旋转.执行此操作的常见方法是在每次迭代时等待几秒钟.

while(true) {
    while(!newEmailArrived()) {
        try {
            Thread.sleep(5000);
        } catch(InterruptedException e) {
        }
    }
    makeNotification();
}
Run Code Online (Sandbox Code Playgroud)

这样做非常好.但是,如果你必须立即工作,睡眠可能是不可能的.

Java 9试图通过引入这个新方法来解决这个问题:

while(true) {
    while(!newEmailArrived()) {
        Thread.onSpinWait();
    }
    makeNotification();
}
Run Code Online (Sandbox Code Playgroud)

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

  • 虽然以上是正确的,但这个例子远非理想.旋转锁适用于您很少等待锁定的情况,如果您正在等待很短的时间; 最多一毫秒.拥有自旋锁的重点是没有正常锁的开销,因为你锁定很多(例如无锁数据结构)并且你无法获得正常锁定开销.在等待新邮件到达时,情况并非如此,在这种情况下,您真的想要使用普通的锁定机制.长话短说,你需要的机会很少. (4认同)

Eug*_*ene 7

我只想在阅读文档及其源代码后添加2美分.这种方法可能会引发一些优化,并且可能不-所以必须小心-你真的不能依靠它-因为这是一个hintCPU超过需求,可能没有最初依靠任何不管怎样...这意味着,它的实际来源代码看起来像这样:

@HotSpotIntrinsicCandidate
public static void onSpinWait() {}
Run Code Online (Sandbox Code Playgroud)

什么这实际上意味着是,这种方法基本上是NO-OP,直至碰到c2 compilerJIT,按照


iga*_*gaz 7

对于一个真实的例子,假设您要实现异步日志记录,即想要记录某些内容的线程不想等待其日志消息被“发布”(例如写入文件),只要最终成功了(因为他们有真正的工作要做。)

Producer(s):
concurrentQueue.push("Log my message")
Run Code Online (Sandbox Code Playgroud)

并说,您决定拥有一个专用的使用者线程,该线程专门负责将日志消息实际写入文件:

(Single)Consumer

while (concurrentQueue.isEmpty())
{
    //what should I do?

}
writeToFile(concurrentQueue.popHead());
//loop
Run Code Online (Sandbox Code Playgroud)

问题是在while块内做什么?Java没有提供理想的解决方案:您可以执行Thread.sleep(),但是要花多长时间,这是很重的;或Thread.yield(),但未指定,或者您可以使用锁或互斥锁*,但这通常太重了,还会减慢生产者的速度(并消除了异步记录的既定目的)。

您真正想要的是对运行时说:“我希望等待的时间不会太长,但是我希望将对其他线程的等待/负面影响的开销降到最低”。那就是Thread.onSpinWait()进入的地方。

如上所示,在支持它的平台上(例如x86),onSpinWait()会被内化为PAUSE指令,这将为您带来所需的好处。所以:

(Single)Consumer

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

}
writeToFile(concurrentQueue.popHead());
//loop
Run Code Online (Sandbox Code Playgroud)

它已经根据经验显示,这可“忙等待”式的循环改善延迟。

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

如果您想除草,您做不到英特尔的规格

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