【问题标题】:Thread::yield vs Thread::onSpinWaitThread::yield vs Thread::onSpinWait
【发布时间】:2025-12-18 18:40:02
【问题描述】:

好吧,标题基本上说明了一切,还有一点我真的很想知道何时使用它们。它可能很简单 - 我已经阅读了他们两个的文档,但仍然无法分辨出太多区别。

这里有像this 这样的答案,基本上是说:

让步对于忙碌的等待也很有用...

我不太同意他们的观点,原因很简单,ForkJoinPool 在内部使用Thread::yield,这是 jdk 世界中最近添加的。

真正困扰我的是 jdk 中的类似用法(StampledLock::tryDecReaderOverflow):

    else if ((LockSupport.nextSecondarySeed() & OVERFLOW_YIELD_RATE) == 0)
        Thread.yield();
    else
        Thread.onSpinWait();
    return 0L;

因此,似乎在某些情况下,一个人会比另一个人更受欢迎。不,我没有一个我可能需要这个的实际示例 - 我实际使用的唯一一个是 Thread::onSpinWait 因为 1)我碰巧忙着等待 2)这个名字几乎是不言自明的,我应该 在忙碌的旋转中使用过它。

【问题讨论】:

  • 您能描述一个需要旋转等待的场景吗?作为 Java 用户而非实现者,我从来不需要这样做。当然,在内核编程中,自旋锁无处不在。但是在 Java 中呢?我想不出我会旋转等待而不是使用锁、监视器或异步回调的用例。 (我并没有刻薄。如果答案几乎没有人应该使用它们中的任何一个,那么询问使用哪一个可能是错误的问题。)
  • @JohnKugelman 我从来没有需要 - 我只知道该方法存在并且它的文档有一个与我所拥有的非常匹配的示例,因此决定使用它, 希望它能以某种方式提高性能。我也从来不需要忙着旋转——感觉就像我绝对应该尝试的。
  • @Torben 考虑到我在该问题中给出了其中一个答案,我不认为这是重复的。
  • API 示例显示了一个在 volatile 标志上旋转的循环。如果您正在实现低级锁定原语,那很好。奇怪的是,大多数阅读它的人都不是。他们应该远离这样的成语。旋转变量仅供专家和初学者使用。

标签: java concurrency java.util.concurrent


【解决方案1】:

阻塞线程时,有几种策略可供选择:自旋、wait()/notify(),或两者的组合。纯粹在变量上旋转是一种非常低延迟的策略,但它可能会使其他争用 CPU 时间的线程饿死。另一方面,wait() / notify() 将为其他线程释放 CPU,但在取消调度/调度线程时可能会花费数千个 CPU 周期的延迟。

那么我们如何才能避免纯粹的自旋以及与取消调度和调度阻塞线程相关的开销呢?

Thread.yield() 提示线程调度程序在另一个具有相同或更高优先级的线程准备好时放弃其时间片。这避免了纯粹的自旋,但不能避免重新调度线程的开销。

最新添加的是Thread.onSpinWait(),它插入特定于体系结构的指令以提示处理器线程处于自旋循环中。在 x86 上,这可能是 PAUSE 指令,在 aarch64 上,这是 YIELD 指令。

这些说明有什么用?在纯自旋循环中,处理器将推测性地一遍又一遍地执行循环,填满管道。当线程正在旋转的变量最终发生变化时,所有的推测工作都将由于内存顺序违规而被抛出。真是浪费!

对处理器的提示可以防止流水线推测性地执行自旋循环,直到之前的内存指令被提交。在 SMT(超线程)的上下文中,这很有用,因为管道将被释放给其他硬件线程。

【讨论】:

  • 所以onSpinWait() 是正确的,如果我们期望另一个线程已经在满足条件的不同 CPU(核心)上运行,而 yield() 是正确的,如果我们期望其他线程不有 CPU 时间。不幸的是,我们不知道,所以问题中显示的示例代码使用一些基于随机的启发式方法来决定何时调用哪个方法。