April 11, 2020
Stateless classes are perfect for multi-threaded programming. They can be called from multiple threads without ever worrying about synchronization of their state since there is none. But how can I write a performant, thread-safe stateless class?
Gson, an open-source library to convert Java Objects into JSON and back from google, is a good example of such a class. Gson uses the following three techniques to implement a performant, thread-safe stateless class:
Let us look at those three techniques in more detail.
Stack confinement is described in the book Java Concurrency in Practice, by Brian Goetz et al.:
If data is only accessed from a single thread, no synchronization is needed. This technique, thread confinement, is one of the simplest ways to achieve thread safety. [...] Stack confinement is a special case of thread confinement in which an object can only be reached through local variables.
So the idea is to only use local variables to achieve thread-safety. To do this we create a new instance at the beginning of our method and store this instance in a local variable.
The following shows how this is done in Gson:
public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOException { try { JsonWriter jsonWriter = newJsonWriter( Streams.writerForAppendable(writer)); toJson(src, typeOfSrc, jsonWriter); } catch (IOException e) { throw new JsonIOException(e); } }
We create a new instance of JsonWriter in the method newJsonWriter for every method call. This instance is only reachable through local variables. So our data is stack confined and therefore thread-safe.
Now since we create a new instance for each method call, the instance creation time becomes performance-critical. To improve this time we use thread-safe caching.
We need to things for this: First a thread-safe hash map and second thread-safe cached elements. For the hash map, we use the class java.util.concurrent.ConcurrentHashMap. This class provides a high performant thread-safe hash map. For the elements, we either use immutable classes or we need to synchronize their methods.
In Gson the type adapters are cached in a ConcurrentHashMap:
private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<TypeToken<?>, TypeAdapter<?>>();
Gson uses both immutable elements like for example com.google.gson.internal.bind.ArrayTypeAdapter and synchronized elements like in com.google.gson.internal.bind.DateTypeAdapter.
Gson has multiple configuration options. To make the configuration thread-safe it can only be configured at construction time. And to make it easy to use only the default configuration can be constructed by the constructor. For all other configurations, Gson uses the builder pattern. The builder pattern encapsulates the creation of a complex object in a separate Builder object.
The following example shows how the GsonBuilder can be used to create a Gson instance with a specific configuration:
Gson gson = new GsonBuilder() .registerTypeAdapter(Id.class, new IdTypeAdapter()) .enableComplexMapKeySerialization() .serializeNulls() .create();
For me, Gson is a good example of how to implement a stateless thread-safe utility class. Gson shows how to implement a thread-safe class by creating a new instance at each method call. Thereby using stack confinement to achieve thread-safety. It shows how to use caching to reduce the object creation time. And it shows how to configure the instance only at construction time using the builder pattern.
© 2020 vmlens Legal Notice Privacy Policy