7 Techniques for Thread-Safe Classes
There are plenty of ways to create thread-safe classes in Java. Here, we dive into state handling, message passing, volatile fields, and more.
Join the DZone community and get the full member experience.
Join For FreeAlmost every Java application uses threads. A web server like Tomcat process each request in a separate worker thread, fat clients process long-running requests in dedicated worker threads, and even batch processes use the java.util.concurrent.ForkJoinPool to improve performance.
It is, therefore, necessary to write classes in a thread-safe way, which can be achieved by one of the following techniques.
No State
When multiple threads access the same instance or static variable, you must somehow coordinate the access to this variable. The easiest way to do this is simply by avoiding instance or static variables. Methods in classes without instance variables do only use local variables and method arguments. The following example shows such a method which is part of the class java.lang.Math:
public static int subtractExact(int x, int y) {
int r = x - y;
if (((x ^ y) & (x ^ r)) < 0) {
throw new ArithmeticException("integer overflow");
}
return r;
}
No Shared State
If you cannot avoid state, do not share the state. The state should only be owned by a single thread. An example of this technique is the event processing thread of the SWT or Swing graphical user interface frameworks.
You can achieve thread-local instance variables by extending the thread class and adding an instance variable. In the following example, the field pool and workQueue are local to a single worker thread.
package java.util.concurrent;
public class ForkJoinWorkerThread extends Thread {
final ForkJoinPool pool;
final ForkJoinPool.WorkQueue workQueue;
}
The other way to achieve thread-local variables is to use the class java.lang.ThreadLocal for the fields you want to make thread-local. Here is an example of an instance variable using java.lang.ThreadLocal:
public class CallbackState {
public static final ThreadLocal<CallbackStatePerThread> callbackStatePerThread =
new ThreadLocal<CallbackStatePerThread>()
{
@Override
protected CallbackStatePerThread initialValue()
{
return getOrCreateCallbackStatePerThread();
}
};
}
You wrap the type of your instance variable inside the java.lang.ThreadLocal. You can provide an initial value for your java.lang.ThreadLocal through the method initialValue().
The following shows how to use the instance variable:
CallbackStatePerThread callbackStatePerThread = CallbackState.callbackStatePerThread.get();
Through calling the method get(), you receive the object associated with the current thread.
Since, in application servers, a pool of many threads is used to process requests, java.lang.ThreadLocal leads to a high memory consumption in this environment. java.lang.ThreadLocal is therefore not recommended for classes executed by the request processing threads of an application server.
Message Passing
If you do not share state using the above techniques, you need a way for the threads to communicate. A technique to do this is by passing messages between threads. You can implement message passing using a concurrent queue from the package java.util.concurrent. Or, better yet, use a framework like Akka, a framework for actor style concurrency. The following example shows how to send a message with Akka:
target.tell(message, getSelf());
And receive a message:
@Override
public Receive createReceive() {
return receiveBuilder()
.match(String.class, s -> System.out.println(s.toLowerCase()))
.build();
}
Immutable State
To avoid the problem where a sending thread changes the message when the message is read by another thread, messages should be immutable. The Akka framework, therefore, has the convention that all messages have to be immutable
When you implement an immutable class, you should declare its fields as final. This not only makes sure that the compiler can check that the fields are in fact immutable but also makes them correctly initialized even when they are incorrectly published. Here is an example of a final instance variable:
public class ExampleFinalField
{
private final int finalField;
public ExampleFinalField(int value)
{
this.finalField = value;
}
}
Use the Data Structures From java.util.concurrent
Message passing uses concurrent queues for communication between threads. Concurrent queues are one of the data structures provided in the package java.util.concurrent. This package provides classes for concurrent maps, queues, dequeues, sets, and lists. Those data structures are highly optimized and tested for thread safety.
Synchronized Blocks
If you cannot use one of the above techniques, use synchronized locks. By putting a lock inside a synchronized block, you make sure that only one thread at a time can execute this section.
synchronized(lock)
{
i++;
}
Beware that when you use multiple nested synchronize blocks, you risk deadlocks. A deadlock happens when two threads are trying to acquire a lock held by the other thread.
Volatile Fields
Normal, nonvolatile fields can be cached in registers or caches. Through the declaration of a variable as volatile, you tell the JVM and the compiler to always return the latest written value. This not only applies to the variable itself, but to all values written by the thread that has written to the volatile field. The following shows an example of a volatile instance variable:
public class ExampleVolatileField
{
private volatile int volatileField;
}
Even More Techniques
I excluded the following more advanced techniques from this list:
Atomic updates: A technique in which you call atomic instructions like compare and set provided by the CPU
java.util.concurrent.locks.ReentrantLock: A lock implementation that provides more flexibility than synchronized blocks
java.util.concurrent.locks.ReentrantReadWriteLock: A lock implementation in which reads do not block reads
java.util.concurrent.locks.StampedLock a nonreeantrant Read-Write lock with the possibility of optimistically reading values.
Conclusion
The best way to achieve thread safety is to avoid shared state. For the state, you need to share you can either use message parsing together with immutable classes or the concurrent data structures together with synchronized blocks and volatile fields. And if you want to test whether your application is thread-safe, try vmlens for free.
I would be glad to hear from you about the techniques you use to achieve thread-safe classes.
Published at DZone with permission of Thomas Krieger, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments