2

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?

Basil Bourque
348k130 gold badges951 silver badges1.3k bronze badges
asked Jan 27, 2018 at 2:34
1
  • 1
    Might help to cite the source of that quote. Commented Jan 27, 2018 at 6:44

3 Answers 3

3
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.

answered Jan 27, 2018 at 2:57
Sign up to request clarification or add additional context in comments.

Comments

2

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
answered Jan 27, 2018 at 3:31

2 Comments

Thanks for the answer. In the first code example that you gave, I am getting the same output without the use of "volatile" keyword and adding the "Thread.sleep()" method after the first print in both the run methods. As per my understanding volatile provides visibility in which all threads see the latest value of the variable. This is achieved with my changes as well, so what difference does the volatile bring here?
@ghostrider volatile can gurantee i = 2 when both thread_1 and thread_2 finish. Without volatile, the result is uncertain, i could be updated to 1 by both thread_1 and thread_2. Yes, may be you take a lot tests and find it works fine without volatile, it does not mean it can work in other enviroments :)
2

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);
 }
}
answered Jan 27, 2018 at 2:56

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.