HuangDong.Li
HuangDong.Li

Reputation: 301

Reading shared variable from another thread (Effective Java #66)

In Effective Java: item 66, Joshua Bloch gave an example about life failure:

// Broken! - How long would you expect this program to run
class StopThread {
    private static boolean stopRequested = false;

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

        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested) {
                    i++;
                }
           }
        });
        backgroundThread.start();

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

As Joshua Bloch said, this program would not terminate. But, if I change i++ into System.out.println(i++), it terminates successfully!

I can't figure out how it happens!

Upvotes: 7

Views: 513

Answers (4)

ashutosh.sr
ashutosh.sr

Reputation: 11

System.out.println() requests a resource to write on the console, which in itself a blocking method… meaning, it will block the backgroundThread() to print() on the console. This is similar to sending an interrupt to it.

Thus, backgroundThread() will become aware of the change in the value of the boolean and stop executing, thereby terminating the Daemon.

Upvotes: 0

yshavit
yshavit

Reputation: 43391

tl;dr: It's most likely an "accidental" side effect of println being synchronized.

First, realize that it's not the case that the thread is guaranteed not to finish; it's that it's not guaranteed that it will finish. In other words, the race condition on stopRequested — caused by the fact that one thread is writing to it, and another thread is reading from it, and there's no synchronization between the two threads — means that the JVM may, but is not required to, let the reader see what the writer has done.

So, why does System.out.println change this? Because it's a synchronized method. This doesn't actually give you any guarantees about stopRequested as far as the JLS is concerned, but it does mean that the JVM has to do some things, like acquiring a(n unrelated) lock, and establishing (unrelated) happens-before edges, that make it more likely that the write to stopRequested will be seen across threads.

Upvotes: 1

Davide Lorenzo MARINO
Davide Lorenzo MARINO

Reputation: 26926

The problem is related to the memory value of the variable stopRequest.

This variable is not defined as volatile.

If you have a two processors the inner thread check the value of stopRequest taken from its registry.

The main thread alter the value of stopRequest in the registry of the other processor.

So the main thread modify a value of stopRequest but the thread see only a copy of it that never changes.

Modified after take a look at the source code of PrintStream (thanks to the commend of ΔλЛ): Using a System.out.print command will use an explicit synchronized block to print the value passed to it this will grant that the value of stopRequest is taken from the main memory and not from the registry of the processor.

Adding a volatile keyword will inform the JVM to take the value from the main memory instead from the registries of the processors and it solve the problem.

Also using the keyword synchronized will solve this problem because any variable used in the synchronized block is taken and update the main memory.

Memory model without volatile (Main thread use Processor 1 and explicit thread use Processor 2)

Processor 1         Processor 2     Main memory
-----------         -----------     -----------
  false                false           false
  true                 false           true       // After setting 
                                                  //stopRequest to true

Defining stopRequest as volatile where all threads read stopRequest from main memory.

    Processor 1         Processor 2     Main memory
-----------         -----------     -----------
       NA                   NA           false
       NA                   NA           true       // After setting 
                                                  //stopRequest to true

Upvotes: 5

Jack
Jack

Reputation: 133587

Since you are not telling the thread that stopRequested is a value that can be modified from outside that thread there are no guarantees that the while will evaluate to the most recent value of the variable.

This is why the volatile keyword is useful in this situation, because it will explicitly enforce that stopRequested value, when read, will be the most recent value set by any thread.

There are further considerations, actually from the point of view of the thread, stopRequested is a loop invariant, since it is never set by only read, so optimization choices should be considered too: if a value is thought not to be modified then there is no reason to evaluate it on each iteration.

Upvotes: 1

Related Questions