-1

In java specification 17.3. Sleep and Yield, it says

It is important to note that neither Thread.sleep nor Thread.yield have any synchronization semantics.

This sentence is the point. If I replace Thread.sleep(100) by System.out.println("") in my test code below, the compiler always read iv.stop every time because System.out.println("") acquires a lock, check this question. Java specification says Thread.sleep does not have any synchronization semantics, so I wonder what makes compiler treat Thread.sleep(100) as same as System.out.println("").

My test code:

public class InfiniteLoop {
    boolean stop = false;

    public static void main(String[] args) throws InterruptedException {

        final InfiniteLoop iv = new InfiniteLoop();

        Thread t1 = new Thread(() -> {
            while (!iv.stop) {
                //uncomment this block of code, loop broken
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
            System.out.println("done");
        });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            iv.stop = true;
        });
        t1.start();
        t2.start();
    }
}

As the comment above says, Thread.sleep() breaks the loop, this is different from the description of the Java specification: why?

Community
  • 1
  • 1
XiangZzz
  • 82
  • 2
  • 10
  • 2
    It isn't different from anything. The compiler remains free not to execute it that way, – user207421 Mar 08 '17 at 16:34
  • @EJP Would you please give a detail explanation? – XiangZzz Mar 08 '17 at 16:42
  • 1
    *The compiler is free to read the field this.done just once* — that does not mean the compiler "must" read it just once ;) – kennytm Mar 08 '17 at 16:42
  • It seems perfectly clear already. What part of 'the compller is free not to execute it that way' don't you understand? – user207421 Mar 08 '17 at 16:47
  • @kennytm My confusion is that why `sleep()` make the compiler read it not just once, the specification says it shouldn't active like this. – XiangZzz Mar 08 '17 at 16:49
  • 1
    No it doesn't. It says that it is **FREE NOT TO** execute it like this. Not that it must not execute it like this. – user207421 Mar 08 '17 at 16:51
  • Multithreaded Java programs are not guaranteed to behave the same way on every platform. ("Write once, run everywhere" always was a bit of a truth-stretcher) Testing will tell you what works _most_ of the time on _your_ platform. Documentation will tell what is supposed to work _every_ time on _every_ platform. – Solomon Slow Mar 08 '17 at 18:22

3 Answers3

3

Let's see what the docs actually says:

The compiler is free to read the field this.done just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value of this.done.

See the highlighted word "free"? "free" means that the compiler can either read this.done once, or not. It's the compiler's choice. That's what "free" means. Your loop breaks because the compiler sees your code and thought "I am going to read iv.stop every time, even though I can read it just once."

In other words, it is not guaranteed to always break the loop. Your code behaves exactly as the docs say.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • How about if I replace the `Thread.sleep()` by `System.out.println("test")`. `System.out.println` acquires a lock, is compiler still "free" to read `iv.stop` just once in this case? – XiangZzz Mar 08 '17 at 17:04
  • @XiangZzz I am not very experienced with threading. But I imagine the compiler will not read it just once. The docs you quoted says things about `sleep` and `yield` so I guess these two methods get special treatment from the compiler. – Sweeper Mar 08 '17 at 17:16
  • 1
    @Sweeper No, they do *not* get special treatment from the compiler That's the whole point. They 'have no synchronization semantics'. – user207421 Mar 08 '17 at 22:40
  • This has got nothing to do with what the methods you are calling actually do. To the compiler, they are just static method calls which take one parameter, and whatever it decides to do is unlikely to affected by which particular method you call – Rodney Mar 09 '17 at 15:52
  • That is just not true @Rodney. A `PrintStream.println(...)` method is Java code that the JVM executes when you call the method. There is no way to separate the behavior of those method calls from your application code if the code calls the method. – Gray Mar 09 '17 at 19:10
1

so I wonder what makes compiler treat Thread.sleep(100) as same as System.out.println("").

Well there is certainly nothing in the language definition that says that they are at all the same. Thread.sleep(...) does not cross any memory barriers while System.out.println(...) does. What you may be seeing is an artifact of how your threaded application is running on your architecture. Maybe the thread gets swapped out because of CPU contention which forces the cache memory to be flushed. If you ran this on a different OS or on hardware with more cores you would most likely not see sleep(...) do anything.

The difference here may also be a compiler optimization. The while loop with nothing in it might not be even checking the value of the stop field since the compiler knows that nothing is updating it inside the loop and it's not volatile. As soon as you add something that does thread state manipulation, it changes the generated code so that the field is then actually paid attention.

Ultimately, the problem is around the publishing of the boolean stop field between threads. The field should be marked as volatile to ensure it is properly shared. As you mentioned, when you call System.out.println(...) this goes in and out of a synchronized block which crosses memory barriers which effectively update the stop field.

Gray
  • 115,027
  • 24
  • 293
  • 354
-1

Though the question is already answered I don't feel the other answers address the confusion in the question.

If we simplify the code to

while (!iv.stop) {
    // do something ....
}

Then the compiler is free (as others have said) to read iv.stop only once. The important points are:

  • To force the compiler to enforce a re-read of iv.stop, it should be declared volatile.

  • Without volatile, the compiler may, or may not change whether it decides to re-read iv.stop as a result of changing the loop contents ("do something...") but that cannot be reliably predicted.

  • You can't infer anything special in this context about the fact that sleep doesn't use locking semantics

(The third point references what I believe to be the confusion in the question)

So with regards to the issue of println() vs sleep(): The fact that sleep doesn't use locking semantics is irrelevant; println doesn't use locking semantics either.

println implementation may use locking to ensure it's own thread-safety, but that fact is not visible in the scope of the calling code (ie your code). (As a side note, sleep implementation ultimately will use some sort of locking deep down in its implementation (in the native code).

From the API perspective, both sleep and println are static methods which take one parameter, so the compiler is likely to be affected the same way by them, with regards to how it performs optimisations in the surrounding code, but like I said you can't rely on that.

Rodney
  • 2,642
  • 1
  • 14
  • 15
  • `System.out` is a `PrintStream` which _is_ a `synchronized` class. Inside of `write(...)` there is a `synchronized` block. – Gray Mar 09 '17 at 16:37
  • When compiling the call to println the compiler doesn't know anything about it's implementation and doesn't care whether something within println calls write, or that write contains a synchronized block. This doesn't have any effect on whether or not "stop" has been cached in the caller's stack frame (which, is ultimately where it would be cached when making a method call, if it decides to cache it) – Rodney Mar 09 '17 at 16:47
  • Dude, I'm sorry but you don't know what you are talking about. If the code executes a `println(...)` call, it enters a `synchronized` block which means it crosses a read memory barrier which invalidates all CPU cached memory forcing the `stop` field to be updated from central memory. When it exits a `synchronized` block it crosses a write memory barrier which forces all dirty memory pages to be published to central memory. `stop` is not cached in the stack frame a all. Also when we talk about cached memory, we aren't talking about stack but rather CPU L1, 2, or 3 memory cache hardware. – Gray Mar 09 '17 at 18:06
  • Lastly @Rodney, you can talk about implementation details only when it pertains to the JVM, native code, or the OS. When you talk about `PrintStream` you can go into the Java code in the JDK to see what it is doing. When you make calls to `PrintStream` methods your code is affected by the locks, etc. that are there. That's _not_ an implementation detail. – Gray Mar 09 '17 at 19:07
  • @Gray This has got nothing whatsoever to do with CPU caches. syncrhonized code has got nothing to do with CPU cache cohereancy. The compiler only compiles one class at a time so it has no special knowledge of what side effect leaf methods may have just because they happen to be in the JDK. If you don't believe me I suggest compiling the code posted by the OP and inspecting the the bytecode. If you actually have some constructive criticism I will consider re-writing my answer to make my point clearer, as the other answer still don't address the actual question asked re `Thread.sleep()` – Rodney Mar 10 '17 at 10:35
  • Also you say "enters a `synchronized` block...forcing `stop` field to be updated" that's the whole point of the question: the OP is asking why this still happens when you call code that *doesn't* enter a syncrhronized block and I am trying to point out that whether or not the code enters a synchronized block is in fact irrelevant – Rodney Mar 10 '17 at 10:43
  • Yeah sorry but that just doesn't make any sense. You can have memory synchronization for a couple of reasons as I say in my answer. But just because he is seeing synchronization in this case doesn't mean that `synchronized` blocks are irrelevant. – Gray Mar 10 '17 at 14:11