How to Write Thread-Safe Code With Kotlin
Kotlin makes it easy to write thread-safe code (especially if we compare it to Java), yet the developer still have to follow some basic rules if he/she wants his/her code to be truly thread-safe. This story will present the main rules to follow, and the tools provided by Kotlin to follow them, but first let’s discuss what is thread-safe code.
Thread-safe code
When the code is meant to be run using several threads, there is a wide range of issues which can occur, mostly regrouped under the following categories:
- Concurrent modification (or race condition): two threads simultaneously try to modify an object, if the object is a collection it will usually lead to a crash of the app; this issue is usually solved by using synchronized sections.
- Deadlock (or livelock): when trying to use synchronized sections, we may create a situation where two threads are waiting on each other; this leads to a freeze of the app.
The golden rule to prevent these issues is to only use immutable objects. An immutable object is an object whose state cannot be changed after its creation. Thus, there is no risk of concurrent modification, and as there is no need of synchronization, we also prevent the risk of deadlocks. That’s also why in this article we will focus on techniques enabling thread-safety without the need of synchronization.
But as it seems easy to say, it’s usually harder to implement, especially when coming from a Java background where objects are traditionally stateful (POJO, beans, etc.). Fortunately, Kotlin provides a lot of syntactic sugar which will help us to make immutable objects, and more generally thread-safe code, without losing too much performance.
Use val as much as possible
When declaring a variable or a class member in Kotlin, we have the choice between two keywords: val and var.
var basically means that the value of the reference or the variable can be changed, while val means it will be constant (initialized only once).
If we want to create an immutable object, then all of its members should be declared with the val keyword. The var keyword should only be used for local variables (i.e. variables which are internal to a function or a method).
Use Kotlin’s immutable collections
Using the val keyword only ensures that the variable will always reference the same object. If I write:
Then “list” will always reference the same list, but it does not imply that the list will be constant: we can still add or remove elements from list. This means that if this list is a member of the class, there is a chance that the code won’t be thread-safe: if two threads try to call a method which changes the content of the list, there will be a concurrent modification exception.
To avoid this, prefer using Kotlin’s immutable collections. Instead of writing:
While the first one is creating a mutable list, the second one will create a list without any methods to add or remove elements from the list. Of course, it implies that we must know what are the elements we want in the list when it is created.
In this case, the list might be initialized like this:
In the first example, we give all the values of the list. For the second one, the values are initialized using a lambda expression whose parameter is the index of the element in the list (the first parameter of the list’s constructor is the desired size of the list). Note: in Kotlin, List is a type of immutable list, while ArrayList is a mutable list.
For the initialization of an immutable list, it’s also possible to declare a mutable list as a local variable, and then store a reference to this list as an immutable list:
In this case, we must ensure that we won’t keep any reference to the mutable list, as it should only be a tool to help us to write the initialization of the immutable list, and not something that we will work with after.
Use the data classes
Data classes are a specific kind of class whose main purpose is to hold data. These data classes can have members and methods, like any other class, but they also provide a really useful method when writing thread-safe code: the copy() method.
Let’s say we declare a data class to hold some data. We want the objects from this class to be immutable, as we are writing thread-safe code, so all the attributes must be declared with the keyword val. Now, we are manipulating an object from this class, and we want to “change” the value of one of its members: we have no other choice to create a new object of the same class with the new value for this member.
With standard classes, it’s pretty annoying to write. But data classes provide syntactic sugar to make it easy. Here’s how it works:
Of course, it’s also possible to encapsulate this behavior to make it looks like we are really modifying the object, while we are actually creating a new object:
Here, we propose a thread-safe changePassword method, which doesn’t really change the password of the user, but makes it look like so by creating a new User which is an exact copy of the original one, but with a new password.
Note: copy() is not making a deep copy of the object, so if the data object is storing a reference to a mutable object, any change to the content of this object will appear for both the original data object and its copy.
Use the synchronized blocks
It’s not always possible to only use immutable objects. Sometimes, if we have to frequently edit the content of an object, creating new copies of our immutable objects in order to change the value of an attribute can be excessively costly.
If this is the case, we will have to switch back to a more classical approach, using synchronized blocks.
Unlike Java, Kotlin doesn’t provide a synchronized keyword allowing us to write mutually exclusive sections. But, this is not really important as Kotlin provides a synchronized function instead which fills the same purpose:
But as I said above, we should avoid using synchronized blocks if possible, as it can lead to deadlock issues. This solution is only beneficial when using immutable objects drastically impacts the performances.
Use the already defined thread-safe structures
If you can’t apply the advice given before, there are still things you can do. The JVM defines several classes and collections which are basically thread-safe.
There are the atomic primitives, like AtomicInteger. Even if several threads try to simultaneously modify the value of an AtomicInteger, we will never have an undefined state with this kind of object caused by a race condition.
And there are also the thread-safe collections, like Collections.synchronizedList.
Personally I wouldn’t recommend using these solutions for two reasons:
- They are not multiplatform: they rely on the JVM, so they can’t be used with Kotlin/Native (and of course Kotlin/JS)
- The synchronized collections are not totally thread-safe, it’s still possible to trigger a ConcurrentModificationException, so the developer usually have to add an extra layer of synchronization
To conclude, we have seen the main tools provide by Kotlin to ensure thread-safety:
- The val keyword helping us to define immutable objects
- A standard library which provides immutable collections
- Some syntactic sugar provided by the data classes, helping us to manipulate immutable objects
And if we don’t have the choice, we can still rely on:
- The synchronization mechanisms of the language, which are quickly introduced here
- The synchronized classes of the Java standard library (if we plan to run on the JVM)
Of course, the topic of multi-threading is really wide and Kotlin provides a lot of convenient features for this that we did not study there. But if you want to enrich your culture, you can give a look to the advanced synchronization mechanisms provided by Kotlin and to the usage of coroutines.
Yet, these solutions are not magical and won’t make you write thread-safe code if you are not careful, that’s why the definition of thread-safe data structures (mostly using immutable objects) should always serve as a basis when writing multi-threaded code.
How to Write Thread-Safe Code With Kotlin
Kotlin makes it easy to write thread-safe code (especially if we compare it to Java), yet the developer still have to follow some basic rules if he/she wants his/her code to be truly thread-safe. This story will present the main rules to follow, and the tools provided by Kotlin to follow them, but first let’s discuss what is thread-safe code.
Thread-safe code
When the code is meant to be run using several threads, there is a wide range of issues which can occur, mostly regrouped under the following categories:
- Concurrent modification (or race condition): two threads simultaneously try to modify an object, if the object is a collection it will usually lead to a crash of the app; this issue is usually solved by using synchronized sections.
- Deadlock (or livelock): when trying to use synchronized sections, we may create a situation where two threads are waiting on each other; this leads to a freeze of the app.
The golden rule to prevent these issues is to only use immutable objects. An immutable object is an object whose state cannot be changed after its creation. Thus, there is no risk of concurrent modification, and as there is no need of synchronization, we also prevent the risk of deadlocks. That’s also why in this article we will focus on techniques enabling thread-safety without the need of synchronization.
But as it seems easy to say, it’s usually harder to implement, especially when coming from a Java background where objects are traditionally stateful (POJO, beans, etc.). Fortunately, Kotlin provides a lot of syntactic sugar which will help us to make immutable objects, and more generally thread-safe code, without losing too much performance.
Use val as much as possible
When declaring a variable or a class member in Kotlin, we have the choice between two keywords: val and var.
var basically means that the value of the reference or the variable can be changed, while val means it will be constant (initialized only once).
If we want to create an immutable object, then all of its members should be declared with the val keyword. The var keyword should only be used for local variables (i.e…