Reputation:
I'm learning about volatile and synchronized in Java and I see that synchronized is used for read-modify-write operations like x++
, and volatile is for read-write operations. And I want to ask you 2 questions. How looks a read-write operation?
And for the second question I have this code:
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();
TimeUnit.SECONDS.sleep(1);
stop = true;
}
}
And I don't understand why this is a read-write operation because the stop variable will be modified from false to true. So isn't this a read-modify-write operation? Thank you!
Upvotes: 0
Views: 236
Reputation: 298103
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.
Upvotes: 4