user9608350
user9608350

Reputation:

Volatile variable for read-write operations in Java

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

Answers (1)

Holger
Holger

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

Related Questions