3 ways to achieve thread safety in Java

1. Why is Thread Safety important in Java?

Java supports multithreading. If different threads can access a class’s state variables concurrently that state could be corrupted. A class is called thread-safe if it gracefully handles concurrent access to its state by throttling or rejecting access attempts by threads where necessary. As we write our code, we must take the necessary care and considerations to ensure that our classes and code is thread-safe.

2. Ways to make a class in Java Thread Safe

Broadly, there are three ways to make classes thread safe in Java. They are –

  1. Synchronized keyword
  2. Atomic variables
  3. Locks

Let’s look at each one in some detail below.

2.1 Using the synchronized keyword

In Java, we can use the synchronized keyword to synchronize methods and blocks to provide thread-safety. The mechanism by which Java achieves this involves intrinsic locks. The interested reader can find more information in the official Java Tutorials: Synchronization. The general idea is illustrated below.

Synchronized method:

public synchronized void method() {
  // do something 
}

Synchronized block:

public void method() {
	// do something
	
	synchronized(this) {
		// do something else
	}
	
	// do something different
}

2.2 Atomic Variables in Java

Java has an entire package containing classes pertaining to atomic variables: java.util.concurrent.atomic. These classes maintain values that can be accessed in a thread-safe manner. If a class has state variables that will only be read and altered independently we can use atomic variables. For example, we can use an AtomicInteger as a simple thread safe counter.

Initialize the counter with initial value 0:

AtomicInteger counter = new AtomicInteger(0);

Increment the counter and retrieve the updated value:

AtomicInteger counter = new AtomicInteger(0);

Decrement the counter and retrieve the updated value:

int value = counter.decrementAndGet();

Get the current value:

int value = counter.get();

Set the current value:

int value = ...;
counter.set(value);

2.3 Locks

Java has an entire package containing classes and interfaces pertaining to locks: java.util.concurrent.locks. In this article, we will focus on the ReadWriteLock interface in Java and its concrete implementation ReentrantReadWriteLock class.

Locks in Java can be used when we have methods that change or read more than one state variable at a time. Since reading state does not alter the state it makes sense that multiple threads can simultaneously read the state. On the other hand, methods that alter the state should only allow a single thread to call that method at a time and prevent other threads from reading the state. The ReadWriteLock allows us to do exactly that.

3. Example – Let’s implement a thread safe Stack in Java using Locks

Stack is a common data structure and used often in Java. Java provides its own implementations of this data structure as part of the collections framework. Here we are implementing this data structure just so that we can demonstrate the thread safety concerns and solutions.

3.1 Stack Interface

public interface Stack<E> {
    void push(E element);
    E pop();
    E peek();
    int size();
}  

3.2 LinkedStack Class

Following is one implementation of the above interface.

public class LinkedStack <E> implements Stack {
    private int size = 0;
    private Node current = null;

       public LinkedStack() {}

    public void push(E element) {
        current = new Node(current, element);
    }

    public E pop() {
        return current == null ? null : current.release();
    }

    public E peek() {
        return current == null ? null : current.element;
    }

    public int size() {
        return size;
    }

    private class Node {
        Node parent;
        E element;

        Node(Node parent, E element) {
            this.parent = parent;
            this.element = element;

            Stack.this.size++;
        }

        E release() {
            Stack.this.current = parent;
            Stack.this.size--;

            return element;
        }
    }
}

Note that this implementation is not thread safe. If one thread pushes an element onto the stack while another thread concurrently pops an element off the stack, the integrity of the stack could become corrupt.

3.3 Concurrent implementation of the LinkedStack Class

Implementing a thread safe subclass of the LinkedStack class using ReentrantReadWriteLock:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ConcurrentLinkedStack<E> extends LinkedStack<E>{

	// the ReadWriteLock
	private final ReadWriteLock lock = new 	ReentrantReadWriteLock();

	public ConcurrentLinkedStack() {
		super();
	}

	public void push(E element) {
	
		// lock the write lock
		lock.writeLock().lock();
		
		// use a try/finally block to ensure that the lock is released
		try{
		
			// push a new value onto top of the stack by calling the push method of the suprerclass
			super.push(element);
			
		// ensure that the lock is released
		} finally {
		
			// release the lock
			lock.writeLock().unlock();
		}
	}

	public E pop() {
	
		// lock the write lock
		lock.writeLock().lock();
		
		// use a try/finally block to ensure that the lock is released
		try{
		
			// pop the value at the top of the stack by calling the pop method of the suprerclass
			return super.pop();
			
		// ensure that the lock is released
		} finally {
		
			// release the lock
			lock.writeLock().unlock();
		}
	}

	public E peek() {
	
		// lock the read lock
		lock.readLock().lock();
		
		// use a try/finally block to ensure that the lock is released
		try{
		
			// peek at the value at the top of the stack by calling the peek method of the superclass
			return super.peek();
			
		// always release the lock
		} finally {
		
			// release the lock
			lock.readLock().unlock();
		}
	}

	public int size() {
	
		// lock the read lock
		lock.readLock().lock();
		
		// use a try/finally block to ensure that the lock is released
		try{
		
			// return the size by calling the size method of the suprerclass
			return super.size();
			
		// ensure that the lock is released
		} finally {
		
			// release the lock
			lock.readLock().unlock();
		}
	}
}

3.4 Creating a thread safe proxy for a non thread safe class

We can implement the proxy pattern to provide thread safety around our original implementation.

What is Proxy Design Pattern – The Proxy Design Pattern essentially involves routing method invocations through an intermediate object to provide additional functionality not present in the target object. Java provides the Proxy class and the InvocationHandler interface to allow developers to easily implement this design pattern.

In this case, our target object would be an object of our original Stack implementation, the LinkedStack class.

To start, let’s create an abstract InvocationHandler. We make this class abstract so that we can resue it with different lock selection criteria.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public abstract class AbstractConcurrentInvocationHandler<E> implements InvocationHandler {

	// the ReadWriteLock
	protected final ReadWriteLock lock = new ReentrantReadWriteLock();
	
	// the instance of the class we want to make thread safe
	protected final E target;
	
	protected AbstractConcurrentInvocationHandler(E target) {
		this.target = target;
	}
		
	// abstract method to select the required lock - subclasses
	// must override this method to implement specific lock selection criteria
	protected abstract Lock selectLock(Method method, Object[] args);
	
	// override the invoke method of the invocation handler interface
	@Override
	public Object invoke​(Object proxy, Method method, Object[] args) throws IllegalAccessException, InvocationTargetException {
	
		// select the appropriate lock
		Lock lock = selectLock(method, args);
		
		return lock == null ?
		
			// no lock - invoke the method on the target directly
			method.invoke(target, args) : 
			
			// lock - invoke the method on the target with the lock 
			invokeWithLock(lock, method, args);
	}
	
	private Object invokeWithLock(Lock lock, Method method, Object[] args) throws IllegalAccessException, InvocationTargetException {
	
		// lock
		lock.lock();
		
		// use a try/finally block to ensure that the lock is released
		try{
		
			// invoke the method on the target
			return method.invoke(target, args);
			
		// ensure that the lock is released
		} finally {
		
			// release the lock
			lock.unlock();
		}
	}
}

Now, let’s define a concrete implementation, specific to our LinkedStack class, by overriding the abstract selectLock method. We’ll determine the lock selection criteria based on the name of the method that is being invoked:

import java.util.concurrent.locks.Lock;
import java.lang.reflect.Method;
...

public class StackHandler<E> extends AbstractConcurrentInvocationHandler {

    public StackHandler() {
        super(new LinkedStack<E>());
    }

    @Override
    protected Lock selectLock(Method method, Object[] args) {
        switch(method.getName()) {
        case "push": return lock.writeLock();
        case "pop": return lock.writeLock();
        case "peek": return lock.readLock();
        case "size": return lock.readLock();
        default: return null;
        }
    }
}

Now we are ready to instantiate a thread safe implementation of our Stack using the Proxy and our StackHandler above.

import java.lang.reflect.Proxy;
...

ClassLoader cl = Stack.class.getClassLoader();
Class[] classes = new Class<?>[] { Stack.class };
InvocationHandler handler = new StackHandler<E>();
Stack<E> stack = (Stack<E>) Proxy.newProxyInstance(cl, classes, handler); 
//thread safe stack is ready for use at this point

This is how we can use Java Proxy and InvocationHandler to provide thread safety around otherwise non safe implementation

3. Performance Considerations

Thread safety incurs a performance penalty. If you know your class will only be accessed by a single thread at a time you should not use a thread safe version if one is available.

4. Conclusion

In this article we discussed thread safety in Java and several ways to make Java classes thread safe.

Leave a Comment

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