Java Concurrency - Synchronnized
Multithread applications are more efficient but if shared resources are not protected, race condition may occur and cause unexpected behavior. We need to properly synchronize
Race Condition
A race condition is the condition of an electronics, software, or other system where the system’s substantive behavior is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when one or more of the possible behaviors is undesirable.
A simple Counter demo class
1 | public class Counter { |
If more than one thread calls setCount or increment method, we will have a race condition.
Note that increment()
method has only one statement count++
. count++
is not atomic. When compiled to bytecode, it becomes multiple operations. First it reads count variable, then add 1. Finally set the result to the original variable.
Sequence of events that cause race condition
count in memory | thread1 | thread2 |
---|---|---|
0 | read count(0) into register | |
0 | read count(0) into register | |
0 | count incremnt by 1 | |
1 | write count to memory. count is now 1 | |
1 | count increment by 1 | |
1 | write cocunt to memory. count is now 1 |
Here the increment method is called by both threads and the result should be 2. However, the result is 1 because of the race condition.
We can use Junit test that demonstrate the effects of race condition. The test below uses a thread pool to run increment()
method 1000 times. The count doesn’t add up to 1000 because of race condition.
1 |
|
The actual count varies for each invocation:
1 | org.opentest4j.AssertionFailedError: |
How to solve Race Condition
To solve race condition issue, we often need to make sure critical section is executed by one thread only at any time. In Java, we can use synchronized
keyword on a method or code block to synchronize resource access. When one thread is execute the code block with synchronized
keyword, other threads need to wait until the thread finish the code block and release the lock.
Synchronized method
Add synchronized keyword to make a method synchronized.
1 | public class Counter { |
To make a method synchronized has two effects:
First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.
Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.
Intrinsic Locks
Every object has a lock associated with it. That is the Intrinsic Lock of the object.
If a thread needs to access an object’s fields, it first needs to own the intrinsic lock of the object. Other threads that do own an object’s intrinsic lock can’t access the object’s fields until the owning thread release the intrinsic lock.
When a thread invokes a synchronized method, it automatically acquires the intrinsic lock for that method’s object and releases it when the method returns.
Synchronized statement
You can use synchronized statement instead of synchronized method to coordinate resource access.
To use synchronized statement, you need to specify the object that provides the intrinsic lock.
use of synchronized statement
1 | public class Counter { |
synchronized statement provides greater flexibility than synchronized method. You use it when you need fine-grain synchronization.
Here is a more practical use of synchronized statement:
1 | public class ComputerCount { |