I'm learning about concurrency in Java and I'm watching a YouTube video. It is an explanation about increment operation in a multi-threading environment. The solution for that could be a volatile variable or a synchronized block. But my question is, if I use a synchronized block do I need to use also volatile variable. Is not enough if I have a synchronized block? I see that the instructor makes the method synchronized and also makes the variable volatile. In my opinion is enough to make the method synchronized because when a thread enters the synchronized method it gets the value of the variable from main memory and when the thread exits the synchronized method it sends the value directly to main memory and it will be available for other threads. So I don't think it is mandatory to make the variable volatile.
This is the code:
public class ClientTest {
public static void main(String[] args) {
ExecutorService executorService = null;
Counter counter = new Counter();
try {
executorService = Executors.newFixedThreadPool(2);
Runnable task1 = () -> {
for (int i = 1; i <= 20000; i++) {
counter.increment();
}
};
Runnable task2 = () -> {
for (int i = 1; i <= 80000; i++) {
counter.increment();
}
};
executorService.submit(task1);
executorService.submit(task2);
executorService.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("Final counter value: " + counter.getCounter());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
… and:
public class Counter {
private int counter;
public int getCounter() {
return counter;
}
public synchronized void increment() {
counter++;
}
}
Is it mandatory to declare the counter variable volatile?
For correct behaviour of the
Counterclass, you will either need to mark thecounterfieldvolatile, or you need to make thegetCounter()methodsynchronizedas well, or alternatively, get rid of theCounterclass and useAtomicInteger.In the specific example of your code, it will probably work correctly, but that is - again probably - because you call
awaitTerminationon the executor service, and the submitted tasks will likely finish before the timeout expires. I think, but do not know for sure, this will establish a happens-before relationship between the tasks executed on the executor threads, and thus your main thread will be guaranteed to see the latest value ofcounter.However, absent such a happens-before relationship, the main thread (or any other thread) is not guaranteed to see the latest value of
counter, it could see any intermediate state, including the initial value of0. To establish such a happens-before, you will need to mark the fieldvolatile, or usesynchronizedon the method, as this will guarantee that the latest written value is read.As an aside, calling
awaitTerminationwithout callingshutdown()on the executor service is not correct.