A program that maintain its correctness in a multi-threaded environment is said to be a program. Thread-Safe Fundamentally, thread safety revolves around proper management of the shared state. In the absence of such proper management, the program could potentially spiral into an invalid state when executed in a multi-threaded environment. Defining Application’s State An application’s state is formed by the collective states of the individual objects that constitute it. An object’s state encompasses the data stored in its static or instance fields, which can influence its externally visible behavior. Furthermore, an object’s state may encompass fields from other dependent objects, creating a complex web of interconnected states within the application. Whether an object’s state need to be managed for concurrent access depends on its usage in your program. If an object is only ever accessed from a single thread, state management might not be needed! If multiple threads access the same mutable state of an object without proper synchronization, your application is technically broken. There are three ways to fix it — Don’t share the object across threads Make the object’s state immutable Implement proper synchronization for accessing the object’s shared state Using good Object Oriented Principles can help us in designing thread-safe classes. Thoughtful implementation of encapsulation, immutability, and precise specification of invariants can significantly reduce the effort required to create thread-safe classes, ultimately streamlining the development process. An important thing to note is that a program consisting of only thread-safe classes may not be thread-safe. Additionally, a program may be thread-safe even when some of its classes are not thread-safe. It all depends on how your objects interact with each other in your program! Defining Thread Safety A thread-safe class behaves correctly even when accessed from multiple threads, regardless of the scheduling or interleaving of the execution. The calling code needs no synchronization on its part when working with a thread-safe class. Thread-safe classes encapsulate the necessary synchronisation, relieving the client from the burden of providing their own. The need for synchronization arises when multiple threads access the same mutable state. However, stateless objects, by virtue of having no state to share, inherently possess thread safety. As a result, . stateless objects are always thread-safe Example of a thread-unsafe class Consider the following class that is used to find a certain number of primes, Java public class PrimeFinder { // Variable to count the number of invocations private int hitCount = 0; public int getHitCount() { return hitCount; } public List<Integer> findPrimes(int count) { hitCount++; // Find 'count' number of primes final List<Integer> primes = new ArrayList<>(); for(int i = 2; i < Integer.MAX_VALUE; i++) { if(isPrime(i)) { primes.add(i); if(primes.size() == count) { break; } } } return primes; } private boolean isPrime(int num) { for(int i = 2; i < num; i++) { if(num % i == 0) { return false; } } return true; } } Note that the instance variable hitCount constitutes the state of any PrimeFinder object. Unfortunately, is not thread-safe, even though it would work fine in a single-threaded environment. This class is vulnerable to , as the operation to increase the , although seemingly atomic, is actually broken down into three distinct steps. Here is an example of an unlucky sequence of events causing to enter an invalid state. PrimeFinder lost updates hitCount hitCount Unlucky interleaving of execution causes hitCount to be 10 when it should be 11 Computation of incorrect results in case of some unlucky timings in a concurrent program is called a . The particular type of race condition that plagues our is called or . To eliminate this race condition, we must increment the hitCount in a single atomic operation. race condition PrimeFinder check-then-act read-modify-write There are several ways to fix the class. One such way is to use a thread-safe class inside . Here is the updated code, PrimeFinder PrimeFinder public class PrimeFinder { private final AtomicInteger hitCount = new AtomicInteger(0); public int getHitCount() { return hitCount.get(); } public List<Integer> findPrimes(int count) { hitCount.getAndIncrement(); // Find 'count' number of primes final List<Integer> primes = new ArrayList<>(); for(int i = 2; i < Integer.MAX_VALUE; i++) { if(isPrime(i)) { primes.add(i); if(primes.size() == count) { break; } } } return primes; } private boolean isPrime(int num) { for(int i = 2; i < num; i++) { if(num % i == 0) { return false; } } return true; } } Using the atomic variable, we can ensure atomic operations on numbers and object references. Since the state of consists only of , which is thread-safe now, the class is thread-safe now! PrimeFinder hitCount PrimeFinder When a single element of state is added to a stateless class, the resulting class will be thread-safe if the state is entirely managed by a thread-safe object. Is it possible to add more state variables to PrimeFinder in a similar manner and still maintain its thread safety? Let us see… Consider the following (updated) class, PrimeFinder public class PrimeFinder { private final AtomicInteger numCache = new AtomicInteger(1); private final AtomicReference<List<Integer>> primeFactorCache = new AtomicReference<>(new ArrayList<>()); // ... public List<Integer> findPrimeFactors(int num) { if(numCache.get() == num) { return new ArrayList<>(primeFactorCache.get()); } // Find prime factors of 'num' final List<Integer> primeFactors = new ArrayList<>(); for(int i = 2; i < num; i++) { if(isPrime(i) && num % i == 0) { primeFactors.add(i); } } numCache.set(num); primeFactorCache.set(new ArrayList<>(primeFactors)); return primeFactors; } // ... } The method is supposed to return prime factors of a number. It also stores a cache of the last number factorized and its factors. We have used atomic classes to store both the and the . findPrimeFactors numCache primeFactorCache Still, our class is not thread-safe! Can you spot the race condition here? Hint: There is a check-then-act race condition here! One invariant of our class is that the list will hold all prime factors of . Since two different variables participate in a single variant, these variables are not independent! In such cases, all the dependent variables must be updated in the same atomic operation! PrimeFinder primeFactorCache numCache To maintain state consistency, ensure that dependent state variables are updated in a single atomic operation. Intrinsic Locking in Java Every Java object can implicitly act as a lock for the purpose of synchronization. These built-in locks are what we call or . intrinsic locks monitor locks There is a built-in mechanism in Java to provide synchronization called a synchronized block. A synchronized block is a set of statements guarded by an intrinsic lock (object reference). Here is its syntax — synchronized (lock) { // Statements working with shared mutable state } The lock is automatically acquired by an executing thread when it enters a synchronized block and is released when the block is exited. Since these intrinsic locks can be held by at most one executing thread at once, they allow exclusive access to the guarded code. A synchronized method is a synchronized block with the entire method as body and object as its lock. this Let us fix our class using intrinsic locks. PrimeFinder public class PrimeFinder { private final AtomicInteger numCache = new AtomicInteger(1); private final AtomicReference<List<Integer>> primeFactorCache = new AtomicReference<>(new ArrayList<>()); // ... public List<Integer> findPrimeFactors(int num) { synchronized (this) { if (numCache.get() == num) { return new ArrayList<>(primeFactorCache.get()); } } // Find prime factors of 'num' final List<Integer> primeFactors = new ArrayList<>(); for(int i = 2; i < num; i++) { if(isPrime(i) && num % i == 0) { primeFactors.add(i); } } synchronized (this) { numCache.set(num); primeFactorCache.set(new ArrayList<>(primeFactors)); } return primeFactors; } // ... } With this, our class is thread-safe again! PrimeFinder With this, we reach the end of this blog. We discussed the fundamentals of thread safety in terms of program state and explored some basic synchronization techniques in Java. If you enjoy reading this blog, consider throwing in a like and subscribe to not miss future articles on Java concurrency! Also published . here