Core Java

Java Concurrency Tutorial – Visibility between threads

When sharing an object’s state between different threads, other issues besides atomicity come into play. One of them is visibility.

The key fact is that without synchronization, instructions are not guaranteed to be executed in the order in which they appear in your source code. This won’t affect the result in a single-threaded program but, in a multi-threaded program, it is possible that if one thread updates a value, another thread doesn’t see the update when it needs it or doesn’t see it at all.

In a multi-threaded environment, it is the program’s responsibility to identify when data is shared between different threads and act in consequence (using synchronization).

The example in NoVisibility consists in two threads that share a flag. The writer thread updates the flag and the reader thread waits until the flag is set:

public class NoVisibility {
    private static boolean ready;
    
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (ready) {
                        System.out.println("Reader Thread - Flag change received. Finishing thread.");
                        break;
                    }
                }
            }
        }).start();
        
        Thread.sleep(3000);
        System.out.println("Writer thread - Changing flag...");
        ready = true;
    }
}

This program might result in an infinite loop, since the reader thread may not see the updated flag and wait forever.

noVisibility

With synchronization we can guarantee that this reordering doesn’t take place, avoiding the infinite loop. To ensure visibility we have two options:

  • Locking: Guarantees visibility and atomicity (as long as it uses the same lock).
  • Volatile field: Guarantees visibility.

The volatile keyword acts like some sort of synchronized block. Each time the field is accessed, it will be like entering a synchronized block. The main difference is that it doesn’t use locks. For this reason, it may be suitable for examples like the above one (updating a shared flag) but not when using compound actions.

We will now modify the previous example by adding the volatile keyword to the ready field.

public class Visibility {
    private static volatile boolean ready;
    
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (ready) {
                        System.out.println("Reader Thread - Flag change received. Finishing thread.");
                        break;
                    }
                }
            }
        }).start();
        
        Thread.sleep(3000);
        System.out.println("Writer thread - Changing flag...");
        ready = true;
    }
}

Visibility will not result in an infinite loop anymore. Updates made by the writer thread will be visible to the reader thread:

Writer thread - Changing flag...

Reader Thread - Flag change received. Finishing thread.

Conclusion

We learned about another risk when sharing data in multi-threaded programs. For a simple example like the one shown here, we can simply use a volatile field. Other situations will require us to use atomic variables or locking.

  • You can take a look at the source code at github.

Xavier Padro

Xavier is a software developer working in a consulting firm based in Barcelona. He is specialized in web application development with experience in both frontend and backend. He is interested in everything related to Java and the Spring framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button