让我们暂停一微秒

低延迟Java应用程序中的许多基准测试涉及必须在一定负载下测量系统。 这就要求保持事件进入系统的稳定吞吐量,而不是在没有任何控制的情况下以全油门将事件泵入系统。

我经常要做的任务之一是在事件之间暂停生产者线程一小段时间。 通常,此时间量为个位数微秒。

那么如何在此时间内暂停线程? 大多数Java开发人员会立即想到Thread.sleep() 但这是行不通的,因为Thread.sleep()仅下降到毫秒,并且比我们暂停所需的时间(以微秒为单位)长一个数量级。

我在StackOverflow上看到一个答案,将用户指向TimeUnit.MICROSECONDS.sleep()以便睡眠少于一毫秒。 引用JavaDoc ,这显然是不正确的:

使用此时间单位执行Thread.sleep 这是一种方便的方法,可以将时间参数转换为Thread.sleep方法所需的形式。

因此,您将无法获得比Thread.sleep(1)相似的1毫秒的暂停。 (您可以尝试下面的代码示例来证明这一点)。

这样做的原因是这种暂停方法(即使线程进入睡眠状态并唤醒它)永远不会足够快或准确到不足一毫秒。

在这一点上我们应该介绍的另一个问题是Thread.sleep(1)到底有多精确? 我们将在稍后再讨论。

当我们想暂停一微秒时,另一个选择是使用LockSupport.parkNanos(x) 使用以下代码停泊1微秒实际上需要约10us。 它比TimeUnit.sleep()/ Thread.sleep()更好,但并不真正适合目的。 100us之后,它的确进入了同一个球场,并且变化仅为50%。

package nanotime;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * Created by daniel on 28/10/2015.
 */
public class NanoTimer {
    public static void main(String[] args) throws InterruptedException {
        long[] samples = new long[100_000];
        int pauseInMillis = 1;

        for (int i = 0; i < samples.length; i++) {
            long firstTime = System.nanoTime();
            LockSupport.parkNanos(pauseInMicros);
            long timeForNano = System.nanoTime() - firstTime;
            samples[i] = timeForNano;
        }

        System.out.printf("Time for LockSupport.parkNanos() %.0f\n", Arrays.stream(samples).average().getAsDouble());
    }
}

解决我们问题的方法是使用System.nanoTime() 。 通过忙于等待对System.nanoTime的调用,我们将能够暂停一微秒。 我们将在一秒钟内看到此代码,但首先让我们了解System.nanosecond()的准确性。 至关重要的是,执行对System.nanoSecond()的调用需要多长时间。

这是一些可以完全做到这一点的代码:

package nanotime;

public class NanoTimer {
    public static void main(String[] args) throws InterruptedException {
        long[] samples = new long[1_000_000];

        for (int i = 0; i < samples.length; i++) {
            long firstTime = System.nanoTime();
            long timeForNano = System.nanoTime() - firstTime;
            samples[i] = timeForNano;
        }

        System.out.printf("Time for call to nano %.0f nanseconds", Arrays.stream(samples).average().getAsDouble());
    }
}

在我的MBP上,从一台机器到另一台机器,数字将有所不同,大约为40纳秒。

这告诉我们,我们应该能够测量到大约40纳秒的精度。 因此,应该很容易测量1微秒(1000纳秒)。

这是忙碌的等待方法,“暂停”了微秒:

package nanotime;

import java.util.Arrays;
/**
 * Created by daniel on 28/10/2015.
 */
public class NanoTimer {
    public static void main(String[] args) throws InterruptedException {
        long[] samples = new long[100_000];
        int pauseInMicros = 1;

        for (int i = 0; i < samples.length; i++) {
            long firstTime = System.nanoTime();
            busyWaitMicros(pauseInMicros);
            long timeForNano = System.nanoTime() - firstTime;
            samples[i] = timeForNano;
        }

        System.out.printf("Time for micro busyWait %.0f\n", Arrays.stream(samples).average().getAsDouble());

    }

    public static void busyWaitMicros(long micros){
        long waitUntil = System.nanoTime() + (micros * 1_000);
        while(waitUntil > System.nanoTime()){
            ;
        }
    }
}

该代码等待一微秒,然后乘以等待时间。 在我的机器上,我得到1,115纳秒,准确度在90%左右。

当您等待更长的时间时,精度会提高,10毫秒需要10,267纳秒,即约97%的准确度,而100毫秒需要100,497纳秒,即约99.5%的准确度。

那么Thread.sleep(1) ,这有多精确?

这是该代码:

package nanotime;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * Created by daniel on 28/10/2015.
 */
public class NanoTimer {
    public static void main(String[] args) throws InterruptedException {
        long[] samples = new long[100_000];
        int pauseInMillis = 1;

        for (int i = 0; i < samples.length; i++) {
            long firstTime = System.nanoTime();
            Thread.sleep(pauseInMicros);
            long timeForNano = System.nanoTime() - firstTime;
            samples[i] = timeForNano;
        }

        System.out.printf("Time for micro sleep %.0f\n", Arrays.stream(samples).average().getAsDouble());
    }
}

1毫秒睡眠的平均时间(以纳秒为单位)为1,295,509。 准确率只有〜75%。 对于几乎所有内容,它可能已经足够好了,但是如果您想要精确的毫秒级暂停,那么忙碌的等待会更好。 当然,您需要记住繁忙的等待,按照定义,繁忙的等待会使您的线程繁忙,并会花费您CPU的时间。

汇总表

暂停方式 1us 10us 100us 1000us / 1ms 10,000us / 10ms
TimeUnit.Sleep() 1284.6 1293.8 1295.7 1292.7 11865.3
LockSupport.parkNanos() 8.1 28.4 141.8 1294.3 11834.2
忙等待 1.1 10.1 100.2 1000.2 10000.2

结论

  • 如果您想暂停不到一毫秒,则需要忙于等待
  • System.nanoSecond()大约需要40ns
  • Thread.sleep(1)的准确率只有75%
  • 忙于等待超过10us以上的时间几乎是100%准确的
  • 繁忙的等待将占用CPU

翻译自: https://www.javacodegeeks.com/2015/11/lets-pause-for-a-microsecond.html

你可能感兴趣的:(让我们暂停一微秒)