如何暂停 Java 线程一小段时间,比如 100 纳秒?

Posted

技术标签:

【中文标题】如何暂停 Java 线程一小段时间,比如 100 纳秒?【英文标题】:How to suspend a java thread for a small period of time, like 100 nanoseconds? 【发布时间】:2012-07-14 23:06:31 【问题描述】:

我知道 Thread.sleep() 可以让 java 线程暂停一段时间,比如几毫秒和几纳秒。但问题是这个函数的调用也会导致开销。

例如,如果我想让一个线程挂起 100 纳秒,我调用 Thread.sleep(0, 100)。这个过程的全部成本是 invocation_cost + 100 纳秒,这可能比我想要的要大得多。我怎样才能避免这个问题,并达到我的目的?

我需要这个的原因是我想离线进行模拟。我分析了一个任务的执行时间;现在我想通过在同一时间段内挂起一个线程来模拟这个执行时间。

谢谢!

【问题讨论】:

您有什么特别的理由要这样做吗?如果是这样,它可能会以不同的方式解决...... 这是一个不寻常的要求。听起来您需要back off strategy 或类似名称。 @gt 仅供参考,在 *** 上发布问题并回复该问题而不是提供答案很烦人。多年来,在项目中出现了许多想要这样做的正当理由。一个实际的原因是在从硬件录制音频的应用程序中保持实时行为,因为硬件的行为可能不一致,或者您可能出于测试目的模拟硬件行为。 @EntangledLoops 感谢您提供示例。我不怀疑这样做有充分的理由。我发现最佳答案通常会考虑提出问题的上下文。一旦 OP 提供了一些背景信息,请查看下面 Peter Lawrey 的回答以查看此示例。 在某些情况下,非常短的睡眠很有用,例如当与只能以低于控制器代码向其传递消息的速率(但仍远超过一千消息一秒钟)。这个问题(及其答案)在这些情况下很有用。 【参考方案1】:

睡眠的粒度一般受线程调度器的中断周期限制。在 Linux 中,这个中断周期在最近的内核中一般为 1ms。在 Windows 中,调度程序的中断周期通常在 10 或 15 毫秒左右

如果我必须在少于此时间的时间内停止线程,我通常会使用忙等待

编辑:我怀疑你会在 jrockit + solaris 上得到最好的结果。 Windows 框上的数字很糟糕。

@Test
public void testWait()
    final long INTERVAL = 100;
    long start = System.nanoTime();
    long end=0;
    do
        end = System.nanoTime();
    while(start + INTERVAL >= end);
    System.out.println(end - start);

【讨论】:

谢谢。如果使用忙等待,您如何控制等待长度?大约 100 纳秒。 >= 应该是 吗? 您是否意识到这个循环占用了 100% 的单个 CPU 内核?这是等待/睡眠的反模式。 您可能应该在这个答案中明确表示它确实会影响 CPU。 如今,您必须大量搜索单核 CPU。显然,它确实可以支撑 CPU 几纳秒。如果您有解决问题的替代方法,请发表评论【参考方案2】:

Thread.sleep() 的另一个问题是它不能保证在指定时间后唤醒。睡眠线程保证在指定的纳秒/微秒内睡眠,但不保证在此之后立即唤醒。既然你说的是纳秒,你可能想试试Object.wait(long, int)

上面的方法我已经非常符合10s纳秒的量级了。

【讨论】:

在这两种情况下,当您调用Thread.sleep()Object.wait() 时,当前运行的线程将进入TIMED_WAITING 模式。这意味着线程放弃了 CPU。在这两种情况下,当等待时间结束时,CPU 很可能会忙于做其他事情。不能保证唤醒线程会立即收到 CPU,即使收到,它仍然需要支付上下文切换时间,根据问题,这是查询者想要避免的。 此外,在Object.wait(timeout, nanos) 的代码中,您可以看到如果nanos > 0 它只会将timeout 增加1。【参考方案3】:

做一个忙碌的等待,(即有一个while循环循环通过这么多数字什么都不做)。在您的程序开始时,您可以计算执行此繁忙等待所花费的时间,并增加或减少它以达到 5 纳秒

我发现 object.wait 在这种频率下会变得毛茸茸,还要注意繁忙的等待解决方案很可能取决于机器因此为什么你应该在程序开始时进行校准步骤

【讨论】:

【参考方案4】:

对于模拟,我不会尝试实时模拟,因为这不会为您提供可重现的结果。即你不能测试你的模拟。

相反,我会使用数据驱动的模拟时钟,并尽可能快地运行所有内容。这为您提供了可重现的结果,并允许您以比实时更快的速度进行仿真(例如快 2 到 100 倍)


怀疑一个线程大约需要 10 微秒。尝试将线程挂起的时间少于此时间是没有意义的。

要忙等待一小段时间,可以试试。

long start = System.nanoTime();
while(start + delay >= System.nanoTime());

注意:作为@EugeneBeresovsky cmets,在您的机器运行了 292 年之后,这可能会溢出,因此您可以选择将其写为

while(System.nanoTime() - start < delay);

这将适用于少于 292 年的延迟。您可以使用 System.currentTimeMillis() 进行更长的延迟。

然而,即使这样也不可靠,因为 System.nanoTime() 在 Centos 5.x 上最多可能需要 300 ns,因此调用它两次将需要比 100 ns 长得多的时间。此外,许多操作系统的分辨率仅为 1000 ns(1 微秒),因此无论您要寻找的延迟如何,此循环都会等待长达 1 微秒。

相反,您可以做的是在一个未优化的短循环中忙于等待。

对于 100 ns 的延迟,我怀疑最好是忙于等待您正在等待的任何内容,而不是创建一个单独的忙循环。

【讨论】:

好帖子。我将 = 以更正代码(回想起来,“>”甚至更好)。我做了一些其他的小改动,以达到 6 (argh) 的最小字符编辑。谢谢。 您的 while 条件存在整数溢出错误。您必须将两个 nanoTime() 结果的差异与某个绝对值进行比较,例如@EntangledLoops,而不是比较两个绝对的 nanoTime 值。 @EugeneBeresovsky 你的机器运行292年后long值会溢出...我会更新我的答案。 在最后一条评论中链接到的文档也明确说明了这一点:To compare two nanoTime values, one should use t1 - t0 &lt; 0, not t1 &lt; t0, because of the possibility of numerical overflow. 阅读您的编辑,人们可能(或可能不会)得到这样的印象,即时间间隔超过 292 年是问题(它们是,但没有程序运行那么久)。那不是我在说的。这是 - 仍然极不可能,但实际上有可能 - nanoTime 在两个调用之间溢出的情况 - 理论上即使调用相隔仅几毫秒或更短。 @EugeneBeresovsky 正确,但是 System.nanoTime() 是机器在 Windows、Linux、MacOSX 上的正常运行时间,因此在您的机器运行 292 年之前不会发生这种情况。【参考方案5】:
public static void busySleep(long nanos)

  long elapsed;
  final long startTime = System.nanoTime();
  do 
    elapsed = System.nanoTime() - startTime;
   while (elapsed < nanos);

【讨论】:

【参考方案6】:

假设一个生产者线程正在填充一个工作缓冲区,比如一个链表。可以调整缓冲区的大小,使其在睡眠-唤醒周期内不会清空,并且 cpu 可以支持在您睡眠时清空缓冲区的消费者线程。您甚至可以增加缓冲区大小,直到您醒来时它不为空。现在,多少睡眠是一个业务决策,因为存在切换开销。上面有很多关于计算的提示!

当然,有几个阻塞并发类,但通常它们的容量是固定的。我不得不相信,阻塞与线程挂起一样昂贵。

【讨论】:

以上是关于如何暂停 Java 线程一小段时间,比如 100 纳秒?的主要内容,如果未能解决你的问题,请参考以下文章

如何让C#程序在执行中暂停一段时间

如何让正在运行的线程暂停一段时间

作业进程线程多线程多核

Java线程总结

Java如何实现线程的暂停和重新启用?求大神

子元素使用了绝对定位,父元素怎么高度自适应