Java Volatile and Visibility
The volatile keyword marks a field so a change by one thread becomes visible to the other threads. An atomic variable updates one value in a single indivisible step.
A volatile flag stops a spinning worker
The worker raises a second volatile flag when it has started and then spins on the stop flag. The main thread waits until it can see the started flag, prints its line, clears the stop flag, joins the worker, and prints the stop line.
public class Main {
public static void main(String[] args) throws Exception {
Flag flag = new Flag();
Thread worker = new Thread(new SpinTask(flag));
worker.start();
while (!flag.started) {
}
System.out.println("Worker is running");
flag.running = false;
worker.join();
System.out.println("Worker stopped");
}
}
class Flag {
volatile boolean started;
volatile boolean running = true;
}
class SpinTask implements Runnable {
private final Flag flag;
SpinTask(Flag flag) {
this.flag = flag;
}
public void run() {
flag.started = true;
while (flag.running) {
}
}
}An AtomicInteger reaches an exact total
Two threads each increment one AtomicInteger five hundred times. Each increment is one indivisible step, so the joined total is exact.
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) throws Exception {
AtomicInteger counter = new AtomicInteger();
Thread first = new Thread(new AddTask(counter));
Thread second = new Thread(new AddTask(counter));
first.start();
second.start();
first.join();
second.join();
System.out.println("Total: " + counter.get());
}
}
class AddTask implements Runnable {
private final AtomicInteger counter;
AddTask(AtomicInteger counter) {
this.counter = counter;
}
public void run() {
for (int i = 0; i < 500; i++) {
counter.incrementAndGet();
}
}
}Example
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) throws Exception {
Gate gate = new Gate();
AtomicInteger counter = new AtomicInteger();
AtomicInteger ready = new AtomicInteger();
Thread first = new Thread(new GatedTask(gate, counter, ready));
Thread second = new Thread(new GatedTask(gate, counter, ready));
Thread third = new Thread(new GatedTask(gate, counter, ready));
first.start();
second.start();
third.start();
while (ready.get() < 3) {
}
gate.open = true;
first.join();
second.join();
third.join();
System.out.println("Total: " + counter.get());
}
}
class Gate {
volatile boolean open;
}
class GatedTask implements Runnable {
private final Gate gate;
private final AtomicInteger counter;
private final AtomicInteger ready;
GatedTask(Gate gate, AtomicInteger counter, AtomicInteger ready) {
this.gate = gate;
this.counter = counter;
this.ready = ready;
}
public void run() {
ready.incrementAndGet();
while (!gate.open) {
}
for (int i = 0; i < 250; i++) {
counter.incrementAndGet();
}
}
}Key points
- A volatile flag suits a stop signal, because each read sees the latest written value.
- Volatile makes a change visible, but it does not make a multi-step update indivisible.
- A second volatile flag lets the main thread see that the worker has started.
- An AtomicInteger performs each increment as one indivisible step.
- Atomic increments from several threads reach an exact total without a lock.
In this example, three workers wait on a volatile gate flag and count their readiness through the atomic counter. The main thread opens the gate once all three are waiting, each worker adds two hundred fifty through the atomic counter, and the printed total is exact.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does the volatile keyword guarantee for the gate flag in the closing example?