👋

Thread.sleep の待機時間

2024/02/17に公開

Windows 上の Java (Kotlin) でのスリープが、想定と異なる挙動でした。
ミリ秒を指定するけど、待機時間が指定に満たないような場合がある。

val t1 = System.nanoTime()
Thread.sleep(100)
val t2 = System.nanoTime()

val dt = t2 - t1
check(dt >= 100_000_000 /* 100 ms */) { "${dt} ns" }
// ==> 99922800 ns

スリープ系の呼び出しは最短待機時間の指定だと思っていたし、
モノトニックタイマーの精度は少なくともマイクロ秒はあると思うので、ちょっと意外。
JVM の問題かもしれないけど、スレッドの待機はそのまま OS の API 呼び出しになっていそうだし。

System.nanoTime() の精度が思ったよりも悪いのだろうか。

https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html#sleep(long)

subject to the precision and accuracy of system timers and schedulers

https://learn.microsoft.com/ja-jp/windows/win32/api/synchapi/nf-synchapi-sleep

dwMilliseconds がシステム クロックの解像度より小さい場合、スレッドは、指定された時間未満でスリープ状態になる可能性があります。 dwMilliseconds が 1 ティックより大きく、2 未満の場合、待機は 1 ティックから 2 ティックまでの任意の場所にできます。

なるほど

https://atmarkit.itmedia.co.jp/bbs/phpBB/viewtopic.php?topic=27114&forum=7

システムタイマー分解能(クロック割り込み間隔):15.625000 ms
マルチメディアタイマー分解能:1 ms
高分解能カウンター分解能:0.000279 ms

これは19年前の情報だけど、分解能がミリ秒単位の整数でない可能性があるのか
昔、自分で調べたときは 50 ms くらいで、分解能を上げると 1 ms とかにできた記憶がある


まとめると、確実にある時間以上を待機したい場合は、OSのティックを調べた上で加算しておかないといけないのか、意外と面倒な仕様だった、、、

Discussion