After reviewing what the main risks are when dealing with concurrent programs (like atomicity or visibility), we will go through some class designs that will help us prevent the aforementioned bugs. Some of these designs result in the construction of thread-safe objects, allowing us to share them safely between threads. As an example, we will consider immutable and stateless objects. Other designs will prevent different threads from modifying the same data, like thread-local variables.
You can see all the source code at github.
1 Immutable objects
Immutable objects have a state (have data which represent the object's state), but it is built upon construction, and once the object is instantiated, the state cannot be modified.
Although threads may interleave, the object has only one possible state. Since all fields are read-only, not a single thread will be able to change object's data. For this reason, an immutable object is inherently thread-safe.
Product shows an example of an immutable class. It builds all its data during construction and none of its fields are modifiable:
In some cases, it won't be sufficient to make a field final. For example, MutableProduct class is not immutable although all fields are final:
Why is the above class not immutable? The reason is we let a reference to escape from the scope of its class. The field 'categories' is a mutable reference, so after returning it, the client could modify it. In order to show this, consider the following program:
And the console output:
Modified Product categories
Since categories field is mutable and it escaped the object's scope, the client has modified the categories list. The product, which was supposed to be immutable, has been modified, leading to a new state.
If you want to expose the content of the list, you could use an unmodifiable view of the list:
2 Stateless objects
Stateless objects are similar to immutable objects but in this case, they do not have a state, not even one. When an object is stateless it does not have to remember any data between invocations.
Since there is no state to modify, one thread will not be able to affect the result of another thread invoking the object's operations. For this reason, a stateless class is inherently thread-safe.
ProductHandler is an example of this type of objects. It contains several operations over Product objects and it does not store any data between invocations. The result of an operation does not depend on previous invocations or any stored data:
In its sumCart method, the ProductHandler converts the product list to an array since for-each loop uses an iterator internally to iterate through its elements. List iterators are not thread-safe and could throw a ConcurrentModificationException if modified during iteration. Depending on your needs, you might choose a different strategy.
3 Thread-local variables
Thread-local variables are those variables defined within the scope of a thread. No other threads will see nor modify them.
The first type is local variables. In the below example, the total variable is stored in the thread's stack:
Just take into account that if instead of a primitive you define a reference and return it, it will escape its scope. You may not know where the returned reference is stored. The code that calls sumCart method could store it in a static field and allow it being shared between different threads.
The second type is ThreadLocal class. This class provides a storage independent for each thread. Values stored into an instance of ThreadLocal are accessible from any code within the same thread.
The ClientRequestId class shows an example of ThreadLocal usage:
The ProductHandlerThreadLocal class uses ClientRequestId to return the same generated id within the same thread:
If you execute the main method, the console output will show different ids for each thread. As an example:
T1 - 23dccaa2-8f34-43ec-bbfa-01cec5df3258
T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d
T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d
T3 - 126b8359-3bcc-46b9-859a-d305aff22c7e
If you are going to use ThreadLocal, you should care about some of the risks of using it when threads are pooled (like in application servers). You could end up with memory leaks or information leaking between requests. I won't extend myself in this subject since the post How to shoot yourself in foot with ThreadLocals explains well how this can happen.
4 Using synchronization
Another way of providing thread-safe access to objects is through synchronization. If we synchronize all accesses to a reference, only a single thread will access it at a given time. We will discuss this on further posts.
We have seen several techniques that help us build simpler objects that can be shared safely between threads. It is much harder to prevent concurrent bugs if an object can have multiple states. On the other hand, if an object can have only one state or none, we won't have to worry about different threads accessing it at the same time.
This post is part of the Java Concurrency Tutorial series. Check here to read the rest of the tutorial.
I'm publishing my new posts on Google plus and Twitter. Follow me if you want to be updated with new content.
Labels: Concurrency, Core, Java