Chapter 03 - Sharing Object

Always synchronisation when share variable cross threads

package org.example.chapter3;  
  
public class NoVisibility {  
    private static boolean ready;  
    private static int value;  
    private static class Reader extends Thread {  
        @Override  
        public void run() {  
            while (true) {  
                if (ready) {  
                    System.out.println(value);  
                    return;  
                }  
                Thread.yield();  
            }  
        }  
    }  
    public static void main(String[] args) {  
        var r = new Reader();  
        r.start();  
        value = 42;  
        ready = true;  
    }  
}

Given this example, in theory the system should return 42. However, the following could happen

  • The thread loop forever: JVM decides to not make the variable ready pass to the Reader class for optimsiation
  • Print out 0. JVM changes the order of the code for optimisation, so it executes the ready first before the value.

This can be avoid by applying proper synchronisation to our variable ready and value even though this is read only in the thread.

[!danger]
Without synchronisation, the compiler, processor can change the order of the operations.

Stale data

Another reason why we need to use Synchronisation for read is reader might get stale data.

Consider the following example:

@NotThreadSafe
public class MutableInteger {
    private int value;
    
    public int get() { return value; }
    public void set(int value) { this.value = value; }
}

If ont thread call set() the same time as the other thread call get(), the data will be stale. As a result, we need to synchronise it

@ThreadSafe
public class SynchronizedInteger {
    @GuardedBy("this") private int value;
    
    public synchronized int get() { return value; }
    public synchronized void set(int value) { this.value = value; }
} 

Non-atomic 64-bit operation

When synchronsiation is not applied to a variable, a thread might see a stale value but at least it will be one of the previous values.

This is true for all the variables except 64 bit variables like long, double if these long and double is not declared as volatile

The reason is with 64-bit variable, JVM allowed to treat it as 2 32-bit operations.

So if the next thread read a long or double and we dont have synchronisation or volatile, it could fetch the 32bit of the last value, and then get updated, and then fetst the 32bit of the new value. Therefore led to unexpected result

Locking and visibility

Pasted image 20250525202328.png

The idea is ThreadB and ThreadA needs to lock on the same lock so that it's guarantee that all threads have the visibility of the variables.

Volatile

See also: Volatile

volatile is a lighter form of synchronisation that allows visibility but not atomicity whereas synchronized allow both

When volatile is applied to a variable, JVM will

  1. Avoid storing that variable to cache or register
  2. Does not reorder this variable

So in the previous example, we can avoid synchronise get and use volatile instead to improve performance

@ThreadSafe
public class SynchronizedInteger {
    @GuardedBy("this") private volatile int value;
    
    public int get() { return value; }
    public synchronized void set(int value) { this.value = value; }
} 

When using volatile variable, we need to keep in mind

  • Make sure that write variable does not depends on current value (value ++) if this is the case, consider using Atomic class
  • Locking is not required for any other reasons while the variable being accessed.

Publishing and Escaping

Publishing is when we intentionally expose the information to other threads or classes

Escaping is when we don't want to expose the variable or information, but it get exposed. In this case we called it escaping

Publishing example

One way to publish is we can just set the variable to be public static

public static Set<Secret> knownSecrets;

public void initialize() {
    knownSecrets = new HashSet<Secret>();
}  

Escaping example

Sometimes we can unintentionally let the variable to escape, for example

class UnsafeStates {
    private String[] states = new String[] {
        "AK", "AL" ...
    };

    public String[] getStates() { return states; }
} 

In here we publish the states, but we escape the mutability of the private field. A better way is to publish the Unmodifiable version or simply states.clone()

Escaping via inheritance

Escaping can also be applied when the infomation of the class get accessed before initialisation happen. The easiest way to reproduce this is via inheritance overriding

class Student {  
    private String name;  
    public Student() {  
        doSomeInitialising();  
        printInfo();  
    }  
  
    protected void doSomeInitialising() {  
        this.name = "student";  
    }  
  
    void printInfo() {  
        System.out.println("Student name is: " + this.name);  
    }  
}  
  
class OverrideStudent extends Student {  
    @Override  
    protected void doSomeInitialising() {  
        // Ignore
    }  
}
public static void main(String[] args) {  
    OverrideStudent overrideStudent = new OverrideStudent();  
}

Will print Student name is: null. This is because the Student is not initialised properly since it's dependent on the doSomeInitialising method.

Solutions:

  1. Make doSomeIntialising() private
  2. or make doSomeInitialising() final

Both of these actions will not allow the parents to override the method that's doing the initialising.

Escaping via anonymous class

Escaping can also be found if we expose it to external classes during constructor, for example

public class ThisEscape {  
    private final int num; // this field is 'escaped' since we have not initialised it before registering the listener  
  
    public ThisEscape(EventSource source) {  
        source.registerListener(  
            new EventListener() {  
                public void onEvent(Event e) {  
                    doSomething(e);  
                }  
        });  
  
        num = 42;   
}  
  
    void doSomething(Event e) {  
        // Handle the event  
        if (num != 42) {  
            throw new IllegalStateException("num is not initialized properly");  
        }  
    }  
}
public class EventSource {  
    void registerListener(EventListener listener) {  
        listener.onEvent(new Event());  
    }  
}
public static void main(String[] args) {  
    var eventSource = new EventSource();  
    ThisEscape thisEscape = new ThisEscape(eventSource);  
}

As the result in here, when source.registerListener called, it will also call the doSomething() in anonymous class.

This doSomething() belong to the main class, however we unintentionally let it trigger before our constructor finished.

Safe constructor practice

The previous example can be modified as following:

public class SafeEscape {  
    final int num;  
    final EventListener listener;  
  
    private SafeEscape() {  
        num = 42;  
        listener = new EventListener() {  
            @Override  
            public void onEvent(Event e) {  
                doSomething();  
            }  
        };  
    }  
  
    public static SafeEscape initalise(EventSource eventSource) {  
        var safeEscape = new SafeEscape(); // Make sure initialise happen  
        eventSource.registerListener(safeEscape.listener);  
        return safeEscape;  
    }  
  
    void doSomething() {  
        if (num != 42) {  
            throw new IllegalStateException("num is not initialized properly");  
        }  
        System.out.println("Success, num is: " + num);  
    }  
}
public static void main(String[] args) {  
    var eventSource = new EventSource();  
//        ThisEscape thisEscape = new ThisEscape(eventSource);  
    SafeEscape safeEscape = SafeEscape.initalise(eventSource);  
}
Success, num is: 42

We use a private constructor to make sure that only we can access this constructor. And then in the initialise we make sure that it's final so it cannot be override when there is a new parent class.

Thread confinement

Thread confinement means every variable within a thread will stay in that thread and not share to another thread. This is the easiest way to achieve threadsafe by design.

Some of the example like JDBC Connection class. The connection pool only return the connection when the connection finished executing. So other threads can't reuse the same connection. That connection is threadsafe by design.

Ad-hoc thread confinement

Developer ensures that a particular object is accessed by only one thread at a time.

volatile is a special case of ad-hoc thread confinement where the developer can confine only 1 modification but not confine the read.

Stack confinement

Make sure that we only use local variable, therefore the variable only stays in the thread itself and not shared

ThreadLocal

ThreadLocal is a way to confine a variable uniquely for a thread. Consider the following example

public class ThreadLocalDemo {  
    static class ParallelWork {  
        private static ThreadLocal<List<Integer>> localInteger = new ThreadLocal<>() {  
            public List<Integer> initialValue() {  
                return List.of(1);  
            }  
        };  
  
        public static void getAndSetInteger() {  
            int value = localInteger.get().getFirst();  
            System.out.printf("Current integer %s\n", value);  
            localInteger.set(List.of(value + 1));  
        }  
    }  
  
    public static void main(String[] args) {  
        var pool = Executors.newFixedThreadPool(3);  
        pool.submit(ParallelWork::getAndSetInteger);  
        pool.submit(ParallelWork::getAndSetInteger);  
        pool.submit(ParallelWork::getAndSetInteger);  
        pool.submit(ParallelWork::getAndSetInteger);  
        pool.shutdown();  
        pool.close();  
    }  
}
Current integer 1
Current integer 1
Current integer 1
Current integer 2

The localInteger is unique for each thread, therefore it's automatically threadsafe. Since we have 3 threads and 4 calls. One of them will have result of 2.

You can think of this as Map<Thread, T>

Immutability

Another way is to make the object immutable so that it's threadsafe

@Immutable
public final class ThreeStooges {
    private final Set<String> stooges = new HashSet<String>();
    public ThreeStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
    }

    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
} 

Note that an immutable object can be made of non-immutable object (stooges) in this case.

However the object is still immutable since there is no way to modify this object.

Final field

Make the field final can also help with immutability. It reduce the possible actions to mutate the fields (however the actual object can still be mutable).

It's a good practice to make all field final whenever possible

Example: using volatile to publish immutable objects

Suppose that we have a function that can cache the factor of the last number (only the last number, it doesn't cache the previous one).

We can use volatile and final to do this

@Immutable
class OneValueCache {
    private final BigInteger lastNumber;
    private final BigInteger[] lastFactors;

    public OneValueCache(BigInteger i,
        BigInteger[] factors) {
        lastNumber = i;
        lastFactors = Arrays.copyOf(factors, factors.length);
    }

    public BigInteger[] getFactors(BigInteger i) {
        if (lastNumber == null || !lastNumber.equals(i))
            // If i is not cached, force recalculate
            return null;
        else
            return Arrays.copyOf(lastFactors, lastFactors.length);
    }
} 
@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
    private volatile OneValueCache cache = new OneValueCache(null, null);

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = cache.getFactors(i);
        if (factors == null) {
            factors = factor(i);
            cache = new OneValueCache(i, factors);
        }

        encodeIntoResponse(resp, factors);
    }
}

The only reason why we can do this is the lastNumber and the lastFactors goes as pairs. Therefore, we can store 1 pair of volatile last result. We can cache it.

This works because this example is unique situation and simple.

Unsafe Publication

Suppose we have this class

// Unsafe publication
public Holder holder;

public void initialize() {
    holder = new Holder(42);
} 
public class Holder {
    private int n;

    public Holder(int n) { this.n = n; }
    public void assertSanity() {
        if (n != n)
            throw new AssertionError("This statement is false.");
    }
} 

In older version of java, assertSanity() check can be false. Why? Assume multiple thread can access to holder object.

ThreadA will initialise the object, ThreadB will try calling assertSanity().

In older java, constructor() is not necessary the first thing to call before an object exists. Therefore ThreadB could see n = 0 or n = null as default value.

However from Java5+, it's by specification that constructor() need to finish before an object is initalised. So any access to the object before that, object will be considered as null

To avoid this, we make the field holder to be final. Since java has special way to treat final object that guarantee the object to exist

Safe publication idioms

To publish an object safely, make sure the following met

  • Initialise from static field
  • Store reference to volatile or AtomicReference
  • Store refernce to a final field
  • Make sure that the object is properly constructed
  • Guard the object in lock