Thread safety

Visibility

  • You must use synchronization in order to ensure visibility of memory writes across threads
  • Locking is not just about mutual exclusion, it is also about memory visibility, and guarantees that any changes in previous thread before releasing lock are actually visible in the next thread which will acquire the lock
  • We want not only to prevent one thread from modifying the state of an object when another is using it, but also to ensure that when a thread modifies the state if ab object, other threads can actually see the changes that were made
  • If we make sure the visibility of a object, will it also ensure visibility of its internal state?
  • See Important happens-before rules

Out-of-thin-air safety

  • When a thread reads a variable without synchronization, it may see a stale value, but at least it sees a value that was actually placed by some other thread rather than some random value, this is called Out-of-thin-air safety
  • This safety applies to all variables except: 64 but numeric variables (double and long) that are not declared volatile
  • JMM specifies fetch and store operations to be atomic, but for non-volatile long and double variables, JVM is permitted to treat a 64 bit read or write as two 32 bit operations
  • Even if are okay with stale data, You must use long and double as volatile or guarded by lock. Otherwise even the stale data represented by them will be wrong

Publishing

  • Publishing means making an object available to code outside of its current scope
    • return data from non-private method
    • passing data into another class method
  • An object that is published when it should not have been, is said to have escaped
  • See Safe Publication

Thread confinement

  • The technique to make data accessible by only single thread is called thread confinement
  • Data which is not shared by multiple threads is automatically thread-safe
  • Ad-hoc thread confinement describes when the responsibility for maintaining thread confinement falls entirely on the implementation
    • Example is to make sure the read-modify-write operations is always done by single thread by design of your program
    • It should be avoided since it is fragile
  • Techniques:
    • Stack Confinement
    • ThreadLocal

Stack Confinement

  • aka within-thread or thread-local usage
  • Local variables are intrinsically confined to the executing thread
  • They exist on the thread’s stack which is not accessible by other threads
  • For primitive variables it is always stack confined since it is not possible to publish its reference (primitive variable hold value not reference)
  • For non-primitive objects, we need to be careful as to not publish its reference
  • Using non-thread safe object in within-thread context is still thread safe

ThreadLocal

  • ThreadLocal allows you to associate a per-thread value with a value-holding object
  • Methods:
    • ThreadLocal.withInitial(() {});
    • threadLocal.set(value);
    • threadLocal.get();
public class TestConcurrency {
 
    private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);
 
    public static void main(String[] args) {
        // Set the value of the thread-local variable for the main thread
        threadLocalValue.set(10);
 
        // Create and start a new thread
        Thread newThread = new Thread(() -> {
            // Get the value of the thread-local variable for this thread
            // will print 0
            System.out.println("Thread-local value in newThread: " + threadLocalValue.get());
        });
        newThread.start();
 
        // Get the value of the thread-local variable for the main thread
        // will print 10
        System.out.println("Thread-local value in main thread: " + threadLocalValue.get());
    }
}