paint-brush
Mastering Object Visibility in Javaby@infinity
203 reads

Mastering Object Visibility in Java

by Rishabh AgarwalFebruary 26th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In a single-threaded environment, any writes made to an object take effect immediately, ensuring that subsequent reads will reflect the updated state of the variable. However these same guarantees do not extend to a multi- threaded system. When the main thread writes to shared objects, these updates might not get visible to reader thread immediately. Other threads may see updated state for some variables while a totally obsolete value for other variables is totally obsolete. In this article, we explore this exact weirdness with object visibility.
featured image - Mastering Object Visibility in Java
Rishabh Agarwal HackerNoon profile picture


Consider the following multi-threaded code,


public class NoVisibility {
    private static boolean ready;
    private static int number;
    private static int ITERATION = 1000000;
    private static class ReaderThread extends Thread {
        public void run() {
            while(!ready) {
                Thread.yield();
            }
            if(number != 56) System.out.println("Bug");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        for(int i=1; i<=ITERATION; ++i){
            if(i%(ITERATION/10)==0) System.out.println("Iteration: " + i);
            number = 0;
            ready = false;
            final Thread thread = new ReaderThread();
            thread.start();
            number = 56;
            ready = true;
            thread.join();
        }
    }
}


Here is a question to all readers — “Executing the main method of NoVisibility, would we ever see ‘Bug’ printed on the terminal?”.


Interestingly the answer is YES!


Here is the output that I got while running this program —


Iteration: 100000
Iteration: 200000
Iteration: 300000
Bug
Iteration: 400000
Bug
Bug
Iteration: 500000
Iteration: 600000
Iteration: 700000
Iteration: 800000
Iteration: 900000
Iteration: 1000000


However counter-intuitive it may feel, this is how thing works in a multi-threaded environment. In this article, we explore this exact weirdness with object visibility in a multi-threaded environment.

Object Visibility

In a single-threaded environment, any writes made to an object take effect immediately, ensuring that subsequent reads will reflect the updated state of the variable. However, these same guarantees do not extend to a multi-threaded system!


In a multi-threaded environment, writes made to shared objects by one thread may not be immediately visible to other threads. Even worse, these writes may not become visible at all! Even more strangely, visibility in a multi-threaded environment is not all or nothing. Other threads may see an updated state for some variables, and, on the other hand, an obsolete value for other variables.


To overcome these challenges, proper synchronization must be used every time shared objects are accessed.

What went wrong with NoVisibility?

Our NoVisibility class is plagued with these same visibility issues. When the main thread writes to shared objects number and ready, these updates might not be visible to the reader thread immediately.


This could lead to problems like —

  • Reader thread looping forever
  • Reordering causes changes made to ready visible before changes are made to number are visible


Lack of proper synchronization inside the NoVisibility class leads to these race conditions.

Synchronisation to ensure Visibility

We discussed Java’s synchronized blocks in this article as a mechanism to ensure atomic operations. synchronized blocks serve another purpose — ‘Object Visibility’. Intrinsic locking can be used to guarantee that one thread sees the effects of another in a predictable manner!


When a thread exits a synchronized block and another thread enters a synchronized block (guarded by the same lock), it ensures that all variables’ values visible to the first thread become visible to the second thread. Importantly, this applies to all variables, regardless of whether they were part of the synchronized block of the first thread.



Locking for Visibility. Guarded using the same lock ‘this’.


Using locking, we ensure memory visibility. It is thus important for both the reading and writing thread to hold proper locks!


Volatile Variables

In Java, volatile variables offer a mechanism to ensure variable visibility across threads. By marking a variable as volatile, we instruct the compiler and runtime to refrain from reordering its operations with other memory operations. Additionally, volatile variables are not kept in registers or caches, preventing them from being hidden from other threads. As a result, volatile variables consistently return the latest written value.


When a write operation is performed on a volatile variable, its impact extends beyond the variable itself. It guarantees the synchronization of not only the variable’s value but also the values of all other variables that were visible up to that point.


Writing to a volatile variable can be likened to exiting a synchronized block, while reading a volatile variable can be likened to entering a synchronized block, all guarded by the same lock.


While locking can guarantee both atomicity and visibility, volatile variables only guarantee visibility.


Often the things that feel intuitively correct in a single-threaded environment may go very wrong in a multi-threaded environment. Visibility of objects is one such things. They are so counter-intuitive that if you don’t know about them it is hard to find race-conditions arising because of them.


I hope you enjoyed reading this blog! Follow for more!


Also published here.