The statement stop = true; is not a “read-write” operation but only a write. It doesn’t read the variable’s old value at all. If stop’s previous value was true, the statement had no effect, without noticing the difference.
A “read-modify-write” operation, also known as “read-update-write” operation implies an operation that reads the previous value, calculates a new value based on it, and writes the new value back to the variable. The problem with this operation, when not using a special atomic update construct, is that by the time, the write is performed, a concurrent update may have happened, so the variable is overwritten with a calculated value which is based on an outdated previous value.
For your boolean variable, “read-modify-write” operations may look like
if(!stop) stop = true;
or
stop = !stop;
but for the first variant, missing a concurrent update would not have much impact, as the statement has no effect if the variable is already true. The second may miss updates if performed concurrently, hence, not reflect the correct number of flip operations, but using concurrent boolean updates for more than a single state transition is error prone in general.
A “read-write” operation, i.e without a “modify/update” in between, would be an operation that reads the old value for later use and writes a new value not based on the old value. Like
Type old = variable;
variable = newValue;
// use old here
which still would be subject to lost updates when not done atomically. Thus, such operation also needs more than a volative variable. E.g. AtomicInteger.getAndSet or VarHandle.getAndSet.
So expanding your example to
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class StopThread {
private static volatile boolean stop;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
while(!stop) {
System.out.println("In while...");
}
}
}).start();
for(int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
boolean old = stop; // broken because
stop = true; // not atomic
System.out.println(old? "other thread was faster": "sent stop signal");
}
}).start();
}
}
}
multiple threads may think that they sent the stop signal.
If you fix the code to
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
public class StopThread {
private static final AtomicBoolean stop = new AtomicBoolean();
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
while(!stop.get()) {
System.out.println("In while...");
}
}
}).start();
for(int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
boolean old = stop.getAndSet(true);
System.out.println(old? "other thread was faster": "sent stop signal");
}
}).start();
}
}
}
exactly one thread will take the responsibility of having sent the stop signal.