Mutexes
In the previous example, we saw how to manage simple counter state using atomic operations. For more complex state, we can use a mutex to safely access data across multiple threads.
Container holds a map of counters; since we want to update it concurrently from multiple threads, we add a ReentrantLock
to synchronize access. Note that locks must not be copied, so if this class
is passed around, it should be done by reference.
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
class Container {
private final ReentrantLock lock = new ReentrantLock();
private final Map<String, Integer> counters = new HashMap<>();
public void inc(String name) {
lock.lock();
try {
counters.put(name, counters.getOrDefault(name, 0) + 1);
} finally {
lock.unlock();
}
}
public Map<String, Integer> getCounters() {
return counters;
}
}
Container holds a map of counters. To modify counters safely, we use the ReentrantLock
to lock the critical section of code.
public class Main {
public static void main(String[] args) throws InterruptedException {
Container container = new Container();
container.inc("a");
container.inc("a");
container.inc("b");
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
container.inc("a");
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
container.inc("a");
}
});
Thread t3 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
container.inc("b");
}
});
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(container.getCounters());
}
}
This function increments a named counter in a loop.
Threads are used to run several tasks concurrently, and they all access the same Container
. Two of them access the same counter.
Running the program shows that the counters updated as expected.
$ javac Main.java
$ java Main
{a=20000, b=10000}
Next, we’ll look at implementing this same state management task using only threads and other synchronization constructs available in Java.