1

I am reading Effective Java of J. Bloch, and in concurrency chapter there is one example:

public class Main {

    private static boolean stop;

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

        new Thread(() -> {
            int i = 0;
            while (!stop) {
                i++;
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);
        stop = true;
    }
}

This example shows that without synchronization child thread won't stop, because it can't see changes made by main thread. But if we change code to this:

public class Main {

    private static boolean stop;

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

        new Thread(() -> {
            while (!stop) {
                System.out.println(i);
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);
        stop = true;
    }
}

application will stop after 1 second. So could someone explain why System.out.println synchronizes threads unlike the first variant doesn't?

ernest_k
  • 44,416
  • 5
  • 53
  • 99
Fairy
  • 509
  • 1
  • 9
  • 27
  • 3
    Where's `i` defined in the second snippet? – ernest_k Mar 03 '18 at 08:35
  • It probably just happens to have some internal function it calls that happens to synchronize the threads. Obviously, it would be crazy to rely on that happening because in the next version of the JVM or on a different CPU it might not work that way. – David Schwartz Mar 03 '18 at 08:37
  • Sorry, just put any String instead of i – Fairy Mar 03 '18 at 08:39
  • If you want a good thread safe flag then use `AtomicBoolean` and you won't need any explicit synchronization. – Oleg Sklyar Mar 03 '18 at 08:43
  • @Fairy Did you call `System.out.println` in the main thread? – xingbin Mar 03 '18 at 08:43
  • @user6690200 no, I call System.out.println in background thread. I just replaced i++ operation with SOP operation – Fairy Mar 03 '18 at 08:48

3 Answers3

3

The book explains that this is because of the hoisting compiler optimization that the first code may run forever.

In short, the code:

while (!stop)
    i++;

may be changed to:

if (!stop)
    while (true)
        i++;

This effectively means that the background (second) thread continues running forever, in the first example; but the optimization isn't applicable to the second code. Synchronization is just proposed as one of the ways to avoid that compiler optimization. More information about this optimization can be found on this question and this one.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
2

Actually, the proper solution would be to declare the variable stop as volatile.

Dorian Gray
  • 2,913
  • 1
  • 9
  • 25
1

Just because your program stops when you test it does not mean it's guaranteed to stop. Your code is broken, but you may get away with it, in part because some CPU architectures (including x86) have a stronger memory model than Java's. On other architectures that Java supports, your program may not stop. This is why thread safety bugs are so insidious. It's very hard to test thread safety, so you just have to know the rules and follow them.

Kevin Krumwiede
  • 9,868
  • 4
  • 34
  • 82