1
public static AtomicInteger num = new AtomicInteger(0);

public static void main(String[] args) throws Throwable {
    Runnable runnable = () -> {
        for (int i = 0; i < 1000000000; i++) {
            num.getAndAdd(1);
        }
    };

    Thread t1 = new Thread(runnable);
    Thread t2 = new Thread(runnable);
    t1.start();
    t2.start();

    System.out.println("before sleep");
    Thread.sleep(1000);
    System.out.println("after sleep");

    System.out.println(num);
}

I want to set the main thread to sleep for 1000ms, but in fact the output will wait until the calculation of the two sub-threads ends before outputting, but when I adjust the time to 100ms, the main thread will not wait for the end of the sub-thread.

apangin
  • 92,924
  • 10
  • 193
  • 247
glongone
  • 23
  • 2

2 Answers2

7

This is a non-trivial effect related to HotSpot Safepoint mechanism.

Background

Usually HotSpot JVM adds a safepoint poll inside loops to allow pausing a thread when the JVM needs to execute a stop-the-world operation. The safepoint poll is not free (i.e. it has some performance overhead), so the JIT compiler attempts to eliminate it when possible. One of such optimizations is to remove a safepoint poll from counted loops.

for (int i = 0; i < 1000000000; i++) is a typical counted loop: it has a monotonic integer loop variable (counter) and the finite number of iterations. JDK 8 JIT compiles such loop without a safepoint poll. However, this is a really long loop; it takes several seconds to complete. While this loop is running, JVM will not be able to stop the thread.

HotSpot JVM uses safepoints not only for GC, but for many other operations. In particular, it stops Java threads periodically when there are cleanup tasks to do. The period is controlled by -XX:GuaranteedSafepointInterval option which defaults to 1000 ms.

What happens in your example

  1. You start two long non-interruptible loops (with no safepoint checks inside).
  2. The main thread goes to sleep for 1 second.
  3. After 1000 ms (GuaranteedSafepointInterval) the JVM attempts to stop Java threads at a safepoint for a periodic cleanup, but can't do it until the counted loops finish.
  4. Thread.sleep method returns from native, discovers that the safepoint operation is in progress, and suspends until the operation ends.

At this point the main thread is waiting for the loops to complete - exactly what you observe.

When you change sleep duration to 100 ms, the guaranteed safepoint happens after Thread.sleep returns, so the method is not blocked.

Alternatively, if you leave 1000 ms sleep, but increase -XX:GuaranteedSafepointInterval=2000, the main thread will not have to wait either.

The fix

-XX:+UseCountedLoopSafepoints option turns off the optimization that eliminates safepoint polling. In this case, Thread.sleep will sleep for 1 second as expected.

Also, if you change int i to long i, the loop will not be treated as counted anymore, so you won't see the mentioned safepoint effect.

Since JDK 10, HotSpot implemented Loop Strip Mining optimization that solves the problem of safepoint polling in counted loops without much overhead. So, your example should work fine out of the box in JDK 10 and later.

The good explanation of the problem and the solution can be found in the description of this issue.

apangin
  • 92,924
  • 10
  • 193
  • 247
  • You've taken 'assume question asker is an expert' to crazy levels here. The odds that the correct move for OP is to mess with `-XX:+UseCountedLoopSafepoints` is infinitesemal. The right solution is to identify this as an X/Y problem. They need to learn about how to do basic thread management using e.g. latches, locks, blockedqueues, or possibly the basics (yield, wait/notify, synchronized), and not about messing with JVM internal switches. – rzwitserloot Apr 13 '21 at 13:12
  • 1
    The question is about unexpected behavior of `sleep`, and this answer explains such behavior. There are no any other assumptions here. – apangin Apr 13 '21 at 14:27
  • First of all, thank you very much for your answer, but I still have a question as to why loops will no longer be considered counts if you change int to long. I did the active throw in the for loop as you said. It is true that it is normal for the main thread to throw when I has a value after sleeping for 1000ms. I would like to know when this loop will have a safe point, is it related to a value or is it related to the time set by JVM – glongone Apr 14 '21 at 05:58
  • 1
    @glongone As to counted loops, this was a JVM limitation before JDK 16, see more details [here](https://bugs.openjdk.java.net/browse/JDK-8223051). JIT compiler in JDK 8 does not put safepoint polls inside all counted loops (all integer loops from A to B with a constant step). – apangin Apr 14 '21 at 20:05
  • Ok, let me take a look,thank you very much – glongone Apr 15 '21 at 02:55
  • Completely disagree with @rzwitserloot, this was an amazing answer! TIL quite a bit more about safepoints! Thanks! – stolsvik Sep 24 '22 at 22:21
0

I want to set the main thread to sleep for 1000ms

And you've done that.

but in fact the output will wait until the calculation of the two sub-threads ends before outputting

You have a computer; it's not magical; its resources are limited. Your 2 threads are 'busy-spinning' - doing work and not sleeping from time to time. Your thread will sleep for 1000 ms, sure. It may not be allowed to run because the system is busy doing other things (such as your 2 calculation threads which are busy-spinning).

If you want to wait for a thread to be done, invoke t1.join(), which will sleep until that thread is done. Then invoke t2.join(), voila.

If you want a thread to do nothing for a while, use a mechanism that tells it to sleep, such as Thread.sleep, wait/notifyAll, or any of the many useful tools in the java.util.concurrent package. Such as polling a blockingqueue.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • 1
    Seems like you've answered the wrong question: the OP didn't ask how to wait for a thread to be done. Also, "busy-spinning" does not explain why `Thread.sleep(1000)` returns after 5 seconds instead of 1. The answer does not explain why `Thread.sleep(100)` works as expected either. – apangin Apr 13 '21 at 12:51
  • No, I answered the question. Read the question. __the main thread will not wait...__ - that part. – rzwitserloot Apr 13 '21 at 13:10