让我们暂停一微秒
低延遲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的時間。
匯總表
| 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
總結
- 上一篇: Linux环境中查看端口的使用情况如何查
- 下一篇: 怎么样才能让路由器中继信号腾达路由器如何