Java: Using Immutable Classes for Concurrent Programming
Let's see how immutable classes make concurrent programming easier while avoiding synchronization blocks and the deadlocks they can lead to.
Join the DZone community and get the full member experience.
Join For FreeImmutable classes make concurrent programming easier. Immutable classes make sure that 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 article, we will look at how to use immutable classes for concurrent programming in Java.
How to Write an Immutable Class
As an example for an immutable class, let's 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 shown in the two fields, line 2 and 3, in the example. This lets the compiler check that the fields were 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. So, the type of the final field must also be immutable like with the class String in the example.
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;
}
}
How to Use Immutable Classes
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 of 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 a synchronization block.
Now we see how we can achieve the same thing using the mutable login class. Again, changing the password in the ConcurrentHashMap:
ivate 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.
Separating Identity and State
In the above examples, the keys of the ConcurrentHashMap defined the identity of the different logins and the values of 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 — 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 always read the latest written value.
When to Use Immutable Classes
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.
What Is Next?
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 hashmap 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.
Published at DZone with permission of Thomas Krieger, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments