14 September 2020 / PROGRAMMING
Let us consider a simple piece of code which is running on two threads.
boolean flag = true
//Thread 1
flag = flase
//Thread 2
while(flag){
//Operations
}
Now we change the value of flag to false from true from thread 1 and say our expectation is that the execution of while loop in thread 2 to stop because of this change. But according to Java specifications this code will not work, because of "Visibility Problem".
To understand this let us first understand the CPU caching structure:
Thread 1 | Thread 2 |
---|---|
#core# | #core# |
local-cache | local-cache |
Shared | Cache |
Consider a CPU with 2 cores where 2 threads are running and both of them have seperate local cache where seperate copy flag variable is stored,so even when the flag is changed true to false in thread 1, the change is not reflected in thread 2 hence our code fails. Hence the change is not visible in thread 2
Solution:
volatile int value = 1
So how does this work?
Upon adding volatile keyword to the flag variable, whenever we are updating its value the data is flushed through the local cache of thread 1 to the shared cache and refreshed back to thread 2. Hence this time the code runs, as thread 2 has now updated value of flag.
Now let us consider a very simple piece of code
volatile int value = 1
Thread 1 | Thread 2 |
---|---|
value++ | value++ |
In this piece of code we are expecting that the value will be 1 at thread1 and 2 in thread2, but to our dismay this will not work even when we are using volatile.
Let us check the execution:
The operation:value++ --> Correponds to 2 steps #readValue(=1) and #addAndWrite(=2)
Steps | Thread 1 | Thread 2 |
---|---|---|
1 | #readValue(=1) | - |
2 | - | #readValue(=1) |
3 | #addAndWrite(=2) | - |
4 | - | #addAndWrite(=2) |
In both the thread initially the values will read as 1 because none of the 2 threads has updated the value yet. At step 3 lets say thread1 updates the value from 1->2. Now after this step any thread reading the value will get 2. Unfortunately thread2 has already read the value and the next possible operation done by thread2 is adding 1.
This happens because we have compound operations, not atomic operations. Read then Write. Since our CPU has multiple cores or we can never be sure of JVM scheduling of threads it is possible that the operations of these two threads are interlined.
So this problem is a "Synchronization Problem"
Solution:
Thread 1 | Thread 2 (active) |
---|---|
synchronized(obj){value++} | synchronized(obj){value++} |
We can use synchronized block which will allow only one thread to be active and execute the code
Solution #2:
AtomicInteger value = new AtomicInteger(1)
Thread 1 | Thread 2 |
---|---|
value.increment() = 2 | - |
- | value.increment() = 3 |
Using AtomicInteger allows us to do the compound operation as single atomic operation, and hence making our operation thread-safe.
More Reference : Oracle Docs
Utsab Chowdhury
Read more posts by this author.