Java Concurrency: Visibility and Synchronized
In this post, gain insight into variable visibility between two threads and what happens when we change a variable that is shared.
Join the DZone community and get the full member experience.
Join For FreePreviously, we examined the happens before guarantee in Java. This guarantee gives us confidence when we write multithreaded programs with regard to the re-ordering of statements that can happen. In this post, we shall focus on variable visibility between two threads and what happens when we change a variable that is shared.
Code Examination
Let’s examine the following code snippet:
import java.util.Date;
public class UnSynchronizedCountDown {
private int number = Integer.MAX_VALUE;
public Thread countDownUntilAsync(final int threshold) {
return new Thread(() -> {
while (number>threshold) {
number--;
System.out.println("Decreased "+number +" at "+ new Date());
}
});
}
private void waitUntilThresholdReached(int threshold) {
while (number>threshold) {
}
}
public static void main(String[] args) {
int threshold = 2125840327;
UnSynchronizedCountDown unSynchronizedCountDown = new UnSynchronizedCountDown();
unSynchronizedCountDown.countDownUntilAsync(threshold).start();
unSynchronizedCountDown.waitUntilThresholdReached(threshold);
System.out.println("Threshold reached at "+new Date());
}
}
This is a bad piece of code: two threads operate on the same variable number without any synchronization. Now the code will likely run forever! Regardless of when the countDown
thread reaches the goal, the main thread will not pick the new value which is below the threshold. This is because the changes made to the number
variable have not been made visible to the main thread. So it’s not only about synchronizing and issuing thread-safe operations but also ensuring that the changes a thread has made are visible.
Visibility and Synchronized
Intrinsic locking in Java guarantees that one thread can see the changes of another thread. So when we use synchronized the changes of a thread become visible to the other thread that has stumbled on the synchronized block.
Let’s change our example and showcase this:
package com.gkatzioura.concurrency.visibility;
public class SynchronizedCountDown {
private int number = Integer.MAX_VALUE;
private String message = "Nothing changed";
private static final Object lock = new Object();
private int getNumber() {
synchronized (lock) {
return number;
}
}
public Thread countDownUntilAsync(final int threshold) {
return new Thread(() -> {
message = "Count down until "+threshold;
while (number>threshold) {
synchronized (lock) {
number--;
if(number<=threshold) {
}
}
}
});
}
private void waitUntilThresholdReached(int threshold) {
while (getNumber()>threshold) {
}
}
public static void main(String[] args) {
int threshold = 2147270516;
SynchronizedCountDown synchronizedCountDown = new SynchronizedCountDown();
synchronizedCountDown.countDownUntilAsync(threshold).start();
System.out.println(synchronizedCountDown.message);
synchronizedCountDown.waitUntilThresholdReached(threshold);
System.out.println(synchronizedCountDown.message);
}
}
Access to the number variable is protected by a lock. Also modifying the variable is synchronized using the same lock.
Eventually, the program will terminate as expected since we will reach the threshold. Every time we enter the synchronized block the changes made by the countdown thread will be visible to the main thread. This applies not only to the variables involved on a synchronized block but also to the variables that were visible to the countdown thread. Thus although the message variable was not inside any synchronized block at the end of the program its altered value got publicized, thus saw the right value printed.
Published at DZone with permission of Emmanouil Gkatziouras, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments