I’ve read that "volatile" in Java allows different threads to have access to the same field and see changes the other threads has made to that field. If that’s the case, I’d predict that when the first and second thread have completely run, the value of "d" will be incremented to 4. But instead, each thread increments "d" to a value of 2.
public class VolatileExample extends Thread {
private int countDown = 2;
private volatile int d = 0;
public VolatileExample(String name) {
super(name);
start();
}
public String toString() {
return super.getName() + ": countDown " + countDown;
}
public void run() {
while(true) {
d = d + 1;
System.out.println(this + ". Value of d is " + d);
if(--countDown == 0) return;
}
}
public static void main(String[] args) {
new VolatileExample("first thread");
new VolatileExample("second thread");
}
}
The results from running this program are:
first thread: countDown 2. Value of d is 1
second thread: countDown 2. Value of d is 1
first thread: countDown 1. Value of d is 2
second thread: countDown 1. Value of d is 2
I understand that if I add keyword "static" the program,
(that is, "private static volatile int d = 0;"), "d" would be incremented to 4.
And I know that’s because d will become a variable that the whole class shares rather than each instance getting a copy.
The results look like:
first thread: countDown 2. Value of d is 1
first thread: countDown 1. Value of d is 3
second thread: countDown 2. Value of d is 2
second thread: countDown 1. Value of d is 4
My question is, why doesn’t "private volatile int d = 0; " yield similar results if volatile is supposed to allow the sharing of "d" between the two threads? That is, if the first thread updates the value of d to 1, then why doesn’t the second thread grab the value of d as 1 rather than as zero?
There are a couple of misunderstandings here. You seem not to properly understand what a thread is, what an instance field is and what a static field is.
An instance field is a memory location that gets allocated once you instantiate a class (ie, a memory location gets allocated for a field
dwhen youVolatileExample v = new VolatileExample()). To reference that memory location from within the class, you dothis.d(then you can write to and read from that memory location). To reference that memory location from outside the class, it must be acessible (ie, notprivate), and then you’d dov.d. As you can see, each instance of a class gets its own memory location for its own fieldd. So, if you have 2 different instances ofVolatileExample, each will have its own, independent, fieldd.A static field is a memory location that gets allocated once a class is initialized (which, forgetting about the possibility of using multiple
ClassLoaders, happens exactly once). So, you can think that a static field is some kind of global variable. To reference that memory location, you’d useVolatileExample.d(accessibility also applies (ie, if it isprivate, it can only be done from within the class)).Finally, a thread of execution is a sequence of steps that will be executed by the JVM. You must not think of a thread as a class, or an instance of the class
Thread, it will only get you confused. It is as simple as that: a sequence of steps.The main sequence of steps is what is defined in the
main(...)method. It is that sequence of steps that the JVM will start executing when you launch your program.If you want to start a new thread of execution to run simultaneously (ie, you want a separate sequence of steps to be run concurrently), in Java you do so by creating an instance of the class
Threadand calling itsstart()method.Let’s modify your code a little bit so that it is easier to understand what is happening:
The line
VolatileExample ve1 = new VolatileExample("first thread");creates an instance ofVolatileExample. This will allocate some memory locations: 4 bytes forcountdownand 4 bytes ford. Then you start a new thread of execution:ve1.start();. This thread of execution will access (read from and write to) the memory locations described before in this paragraph.The next line,
VolatileExample ve2 = new VolatileExample("second thread");creates another instance ofVolatileExample, which will allocate 2 new memory locations: 4 bytes for ve2’scountdownand 4 bytes for ve2’sd. Then, you start a thread of execution, which will access THESE NEW memory locations, and not those described in the paragraph before this one.Now, with or without
volatile, you see that you have two different fieldsd: each thread operates on a different field. Therefore, it is unreasonable for you to expect thatdwould get incremented to 4, since there’s no singled.If you make
da static field, only then both threads would (supposedly) be operating on the same memory location. Only thenvolatilewould come into play, since only then you’d be sharing a memory location between different threads.If you make a field
volatile, you are guaranteed that writes go straight to the main memory and reads come straight from the main memory (ie, they won’t get cached on some — extremely fast — processor-local cache, the operations would take longer but would be guaranteed to be visible to other threads).It wouldn’t, however, guarantee that you’d see the value 4 stored on
d. That’s becausevolatilesolves visibility problem, but not atomicity problems:increment = read from main memory + operation on the value + write to main memory. As you can see, 2 different threads could read the initial value (0), operate (locally) on it (obtaining 1), then writing it to the main memory (both would end up writing 1) — the 2 increments would be perceived as only 1.To solve this, you must make the increment an atomic operation. To do so, you’d need to either use a synchronization mechanism — a mutex (
synchronized (...) { ... }, or an explicit lock) — or a class designed specifically for this things:AtomicInteger.