Title here
Summary here
Java provides built-in support for atomic operations through the java.util.concurrent.atomic
package. We’ll use this package to create an atomic counter that can be safely accessed by multiple threads.
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class AtomicCounters {
public static void main(String[] args) throws InterruptedException {
// We'll use an AtomicLong to represent our (always-positive) counter.
AtomicLong ops = new AtomicLong();
// We'll use an ExecutorService to manage our threads.
ExecutorService executor = Executors.newFixedThreadPool(50);
// We'll start 50 threads that each increment the counter exactly 1000 times.
for (int i = 0; i < 50; i++) {
executor.submit(() -> {
for (int c = 0; c < 1000; c++) {
// To atomically increment the counter we use incrementAndGet().
ops.incrementAndGet();
}
});
}
// Shutdown the executor and wait for all threads to finish.
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// Here no threads are writing to 'ops', but using get() it's safe to
// atomically read a value even while other threads are (atomically) updating it.
System.out.println("ops: " + ops.get());
}
}
We expect to get exactly 50,000 operations. Had we used a non-atomic long and incremented it with ops++
, we’d likely get a different number, changing between runs, because the threads would interfere with each other. Moreover, we’d get data race conditions.
To run the program:
$ javac AtomicCounters.java
$ java AtomicCounters
ops: 50000
Next, we’ll look at locks, another tool for managing state in concurrent programs.