This article explains why it is not possible to make a 1 nanosecond sleep in JVM. Instead, you may achieve an unguaranteed small pause which depends on the number of active threads.

Available options

The JVM offers various techniques to pause or delay a thread for a specified duration:

After reviewing the implementation of all methods in the available code of OpenJDK 9, this list can be simplified:

  • Object.wait​(0, x) is Object.wait(1);
  • Thread.sleep(0, x) is Thread.sleep(0) when x less than 500,000 and Thread.sleep(1) otherwise;
  • TimeUnit.sleep​(long timeout) is simple calling Thread.sleep(long millis, int nanos).

Benchmarking

The benchmark was run on an Apple laptop:

Benchmark result on Apple laptop
Benchmarkns/opError
LockSupport.parkNanos.(1)10553.701± 309.104
Object.wait(0,1)1351918.387± 7,285.491
Thread.onSpinWait()3.271± 0.047
Thread.sleep(0)258.652± 3.411
Thread.yield()225.066± 3.808

and a server running Debian Sid:

Benchmark result on server on Debian Sid
Benchmarkns/opError
LockSupport.parkNanos.(1)288.112± 1.606
Object.wait(0,1)1120201.743± 2,152.094
Thread.onSpinWait()2.496± 0.003
Thread.sleep(0)183.340± 2.365
Thread.yield()170.595± 2.303

The anomalous behavior of LockSupport.parkNanos(1) requires further investigation with varying values.

Detailed Benchmark result of LockSupport.parkNanos()
PlatformBenchmarkns/opError
macOSLockSupport.parkNanos.(1)10553.701± 309.104
DebianLockSupport.parkNanos.(1)288.112± 1.606
macOSLockSupport.parkNanos.(10)13,006.281± 73.393
DebianLockSupport.parkNanos.(10)285.572± 0.784
macOSLockSupport.parkNanos.(100)12976.135± 139.048
DebianLockSupport.parkNanos.(100)4674.412± 237.567
macOSLockSupport.parkNanos.(1000)5795.405± 132.921
DebianLockSupport.parkNanos.(1000)55064.592± 245.676
macOSLockSupport.parkNanos.(10000)6127.251± 203.732
DebianLockSupport.parkNanos.(100000)54860.746± 593.290

It is evident that LockSupport.parkNanos(1) is highly unpredictable and dependent on the platform used. The only guarantee is that a larger value will result in a longer pause.

Benchmark Results

To summarize the benchmark results:

  • Thread.onSpinWait() exists since Java 9 and takes only a few nanoseconds;
  • Thread.yield() is faster than Thread.sleep(0) by approximately 13%, but it still takes a few hundred nanoseconds;
  • Object.wait(0, 1) results in approximately 1 millisecond of sleep;
  • LockSupport.parkNanos(x) has unpredictable behavior.

Based on these results, it can be concluded that the JVM cannot guarantee small pauses. There are two options available, but both of them depend on the machine and/or platform:

  • either a small constant value such as Thread.onSpinWait(), Thread.yield(), or Thread.sleep(0);
  • or to suggest a pause, by LockSupport.parkNanos(x), but keep in mind that its behavior cannot be guaranteed.

Multithreaded environment

I analyzed the benchmark in a clean environment with only one thread. However, the benchmark can be run with a desired number of concurrent threads. The log of the multithreaded benchmark was run on Debian on an Intel(R) Xeon(R) CPU E5-1650 v3 @ 3.50GHz.

Multithreaded benchmark
ThreadsThread.onSpinWait()Thread.yield()Thread.sleep(0)
12.496981170.287654180.741691
22.512097175.279333191.246126
42.679693206.098715238.892798
83.160967308.714533332.272579
164.666055694.989630730.951726
3210.7820382733.3489602815.557913
6422.3097197988.4583007791.860280
12838.41747018454.35051222313.944936
25672.33157835429.90728437822.168572

This demonstrates that the pause time depends on the number of active threads:

  • Thread.onSpinWait() causes a pause of 2.5 nanoseconds for 1 thread, 22.3 nanoseconds for 64 threads, and 72 nanoseconds for 256 threads;
  • on the other hand, Thread.yield() causes a pause of 170 nanoseconds for 1 thread, approximately 8 microseconds for 64 threads, and 35 microseconds for 256 threads;
  • and finally, the relation between Thread.sleep(0) and Thread.yield() holds the assumption.

Is it possible to predict? No. According to the documentation, Thread.activeCount() returns an estimated number of active threads, and the returned value may change dynamically while the method traverses internal structures. This means that it is seemingly impossible to compute in advance the number of required Thread.onSpinWait() or Thread.yield() calls to achieve the desired pause.

Conclusion

The JVM cannot guarantee sleep for very short periods of time.

The best available option is Thread.onSpinWait(), which was introduced in Java 9. It compiles into a CPU instruction similar to PAUSE if the target CPU supports it, and into nothing if it doesn’t. When it’s supported, the resulting pause depends on the number of active threads, but it is on the dozen-nanoseconds scale.

The second usable option is Thread.yield(), which works like a hint to the scheduler that this thread should be skipped. It may ignore or follow the instruction. Doing so requires context switching, which can take microseconds in real-life applications.