Update
Because you seem to want an explanation for the specific case of "2 3 2"
- X increments
i
(now 1) - Y increments
i
(now 2) - X reads
i
(X loads 2) - Y reads
i
(Y loads 2) - Y prints the value previously loaded (prints 2)
- Z increments
i
, readsi
, printsi
(prints 3) - X prints the value previously loaded (prints 2)
The point is: System.out.print(" " + count)
isn't atomic. The thread can be preempted after performing a volatile read and before printing the value.
If you want to prevent duplicate values from being printed, you have to perform the volatile read inside the lock:
public void increment() {
int localCount;
synchronized (lock) {
count = count + 1;
localCount = count; // volatile load
}
System.out.print(" " + localCount);
}
This wouldn't prevent values from being printed out of order though. In order to print them in order, without duplicates, you'd have to move the print
into the lock as well.
Old Answer
The print statement is outside the lock.
Consider the code running inside System.out.print(" " + count)
.
- A thread X increments
i
- Thread X evaluates the
print
arguments and performs a volatile read on thecount
variable, and loads the value2
. - Thread X is preempted by Thread Y, which increments
i
- Thread Y loads
i
(which is now3
), calls the print method which runs to completion. - Thread Y is preempted, and Thread X now runs
print
to completion, which prints 2.
This would make the numbers show out of order, such as "3 2 4".
Some numbers might also be repeated when:
- Thread X increments
i
(which is now 2) - Thread Y increments
i
(which is now 3) - Thread X prints
i
(which is 3) - Thread Y prints
i
(which is 3)