February 13, 2018
Category: Programming concurrent Java
Immutable classes make concurrent programming easier. Immutable classes make sure that the values are not changed in the middle of an operation without using synchronized blocks. By avoiding synchronization blocks you avoid deadlocks. And since you are always working with an unchangeable consistent state you avoid race conditions. In the following, we will look at how to use immutable classes for concurrent programming in Java.
As an example for an immutable class we implement a class storing the login credentials of a user:
public class ImmutableLogin { private final String userName; private final String password; public ImmutableLogin(String userName, String password) { super(); this.userName = userName; this.password = password; } public String getUserName() { return userName; } public String getPassword() { return password; } }
When you implement an immutable class you declare its fields as final as the two fields, line 2 and 3, in the example. This lets the compiler check that the fields are not modified after the constructor of the class was called. Note that final is a field modifier. It makes the field itself immutable not the object the field references to. So the type of the final field must also be immutable like in the example the class String.
The following shows a mutable class storing the same information:
public class MutableLogin { private String userName; private String password; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
First, let us see how we can share the immutable login data between multiple threads using a java.util.concurrent.ConcurrentHashMap. To change the login data we use the method compute:
private final ConcurrentHashMap<String,ImmutableLogin> mapImmutableLogin = new ConcurrentHashMap<String,ImmutableLogin>(); public void changeImmutableLogin() { mapImmutableLogin.compute("loginA", (String key , ImmutableLogin login ) -> { return new ImmutableLogin(login.getUserName() , "newPassword"); }); }
As you probably expected, we need to copy the ImmutableLogin to change it. The compute method uses a synchronized block internally to make sure that a value for a given key is not changed in parallel by multiple threads.
The following shows an example for reading the changed login data from the ConcurrentHashMap using get:
public void readImmutableLogin() { ImmutableLogin immutableLogin = mapImmutableLogin.get("loginA"); // read from the object immutableLogin }
Reading the data can directly operate on the ImmutableLogin class without synchronization block.
Now we look how we can achieve the same using the mutable login class.Again changing the password in the ConcurrentHashMap:
private final ConcurrentHashMap<String,MutableLogin> mapMutableLogin = new ConcurrentHashMap<String,MutableLogin>(); public void changeMutableLogin() { MutableLogin mutableLogin = mapMutableLogin.get("loginA"); synchronized(mutableLogin) { mutableLogin.setPassword("newPassword"); } }
and reading the data:
public void readMutableLogin() { MutableLogin mutableLogin = mapMutableLogin.get("loginA"); synchronized(mutableLogin) { // read from the object mutableLogin } }
To make sure that the MutableLogin object does not get changed while reading we need to synchronize the reading and writing using the same monitor. In the examples, we use the MutableLogin object as the monitor. To avoid a nested synchronized block we use the get method for modifying the MutableLogin instead of the compute method.
In the above examples, the keys of the ConcurrentHashMap defined the identity of the different logins and the values the current state of the login. In the case of the MutableLogin class, each key has exactly one MutableLogin object. In the case of the ImmutableLogin, each key has different ImmutableLogin objects at different points in time. Mutable classes represent both identity and state while immutable classes represent only the state and we need a separate class to represent the identity. So using Immutable classes leads to a separation of identity and state.
The following shows how to encode the identity of the login in the class Login and the state in the class ImmutableLogin:
public class Login { private volatile ImmutableLogin state; public Login(ImmutableLogin state) { super(); this.state = state; } public synchronized void change(Function<ImmutableLogin,ImmutableLogin> update ) { state = update.apply(state); } public ImmutableLogin getState() { return state; } }
The change function uses a synchronized block to make sure that only one thread is updating the Login object at a given time. The field state must be declared as volatile to make sure that you read always the latest written value.
Modifying immutable state consists of a copying the immutable object. This allows us to read the state without the need of synchronization blocks. So you should use immutable classes when it is feasible to copy the object for every modification and you need read-only access to the data.
So far all examples we have looked at used synchronized blocks for changing the state of the class holding the immutable object. In the next blog post, we will see how to implement a concurrent hash map with immutable classes for the hash array elements using compare-and-swap operations instead.
In the meantime, if you want to test whether your application is thread-safe, try vmlens for free. I would be glad to hear from you about how you use immutable classes.
© 2020 vmlens Legal Notice Privacy Policy