Reputation: 2238
I am trying to learn volatile
field modifier in multi-threading. I came across this statement:
Volatile is preferred in cases when one thread reads and writes a shared variable and other threads just read the same. Whereas if there are more than 2 threads performing read and write both on the shared variable then only volatile is not enough, you need to have synchronization as well.
I am aware that volatile
provides visibility and happens-before guarantee, but is it possible to give a simple small example of code to demonstrate the above statements wherein a synchronized block is needed?
Upvotes: 2
Views: 176
Reputation: 28289
Let's say you have int i
and two threads, you expect every one read i
and set i = i + 1
.
Like this:
public class Main {
private static volatile int i = 0;
public static void main(String[] args) throws Exception{
Runnable first = new Runnable() {
@Override
public void run() {
System.out.println("Thread_1 see i = " + i);
i++;
System.out.println("Thread_1 set i = " + i);
}
};
Runnable second = new Runnable() {
@Override
public void run() {
System.out.println("Thread_2 see i = " + i);
i++;
System.out.println("Thread_2 set i = " + i);
}
};
new Thread(first).start();
new Thread(second).start();
}
}
The result is:
Thread_1 see i = 0
Thread_2 see i = 0
Thread_1 set i = 1
Thread_2 set i = 2
As you see, Thread_2 get 0
and set 2
(because Thread_1 has updated i
to 1
), which is not expected.
After adding syncronization,
public class Main {
private static volatile int i = 0;
public static void main(String[] args) throws Exception{
Runnable first = new Runnable() {
@Override
public void run() {
synchronized (Main.class) {
System.out.println("Thread_1 see i = " + i);
i++;
System.out.println("Thread_1 set i = " + i);
}
}
};
Runnable second = new Runnable() {
@Override
public void run() {
synchronized (Main.class) {
System.out.println("Thread_2 see i = " + i);
i++;
System.out.println("Thread_2 set i = " + i);
}
}
};
new Thread(first).start();
new Thread(second).start();
}
}
It works:
Thread_2 see i = 0
Thread_2 set i = 1
Thread_1 see i = 1
Thread_1 set i = 2
Upvotes: 2
Reputation: 51369
There are a lot of such examples... Here's one:
volatile int i = 0;
// Thread #1
while (true) {
i = i + 1;
}
// Thread #2
while (true) {
Console.WriteLine(i);
}
In this case, Thread #1 and Thread #2 are both reading the variable i
, but only Thread #1 is writing to it. Thread #2 will always see an incrementing value of i
.
Without the volatile keyword, you will occasionally see strange behavior, usually on multiprocessor machines or multicore CPUs. What happens (simplifying slightly here) is that Thread #1 and #2 are each running on their own CPU and each gets it's own copy of i
(in it's CPU cache and/or registers). Without the volatile keyword, they may never update each other about the changed value.
Contrast with this example:
static volatile int i = 0;
// Thread #1
while (true) {
i = i + 1;
}
// Thread #2
while (true) {
if (i % 2 == 0)
i == 0;
else
Console.WriteLine(i);
}
So here, Thread #1 is trying to monotonically increment i
, and Thread #2 is either going to set i
to 0 (if i
is even) or print it to the console if i is odd. You would expect that Thread #2 could never print an even number to the console, right?
It turns out that that is not the case. Because you have no synchronization around the access to i
, it is possible that Thread #2 sees an odd value, moves to the else
branch, and then Thread #1 increments the value of i
, resulting in Thread #2 printing an even number.
In this scenario, one way of addressing the problem is to use basic locking as a form of synchronization. Because we cannot lock on a primitive, we introduce a blank Object to lock on:
static volatile int i = 0;
static Object lockOnMe = new Object();
// Thread #1
while (true) {
lock (lockOnMe) {
i = i + 1;
}
}
// Thread #2
while (true) {
lock (lockOnMe) {
if (i % 2 == 0)
i == 0;
else
Console.WriteLine(i);
}
}
Upvotes: 2
Reputation: 719641
public class TwoInts {
private volatile int i1;
private volatile int i2;
public void set(int i1, int i2) {
this.i1 = i1;
this.i2 = i2;
}
public boolean same() {
return i1 == i2;
}
}
Now, if you have one thread doing this:
while (true) {
twoInts.set(i, i);
i++;
}
and a second thread doing this:
while (true) {
if (!twoInts.same()) {
System.out.println("Ooops!!");
}
}
then you will observe the problem that the quoted text is talking about. And if you rewrite the TwoInts
class to make the methods synchronized
then the "Oooops!!" messages will stop.
Upvotes: 3