Mutexes in Minitab
The previous example demonstrated managing simple counter state using atomic operations. For more complex state, we can use a synchronized
keyword or a ReentrantLock
to safely access data across multiple threads.
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
// Container holds a map of counters; since we want to
// update it concurrently from multiple threads, we
// add a ReentrantLock to synchronize access.
class Container {
private final ReentrantLock lock = new ReentrantLock();
private Map<String, Integer> counters;
public Container() {
counters = new HashMap<>();
counters.put("a", 0);
counters.put("b", 0);
}
// Lock before accessing counters; unlock it at the end of the method
public void inc(String name) {
lock.lock();
try {
counters.put(name, counters.get(name) + 1);
} finally {
lock.unlock();
}
}
public Map<String, Integer> getCounters() {
return counters;
}
}
public class Mutexes {
public static void main(String[] args) throws InterruptedException {
Container c = new Container();
// This method increments a named counter in a loop
Runnable doIncrement = (String name, int n) -> {
for (int i = 0; i < n; i++) {
c.inc(name);
}
};
// Run several threads concurrently; note
// that they all access the same Container,
// and two of them access the same counter.
Thread t1 = new Thread(() -> doIncrement.run("a", 10000));
Thread t2 = new Thread(() -> doIncrement.run("a", 10000));
Thread t3 = new Thread(() -> doIncrement.run("b", 10000));
t1.start();
t2.start();
t3.start();
// Wait for the threads to finish
t1.join();
t2.join();
t3.join();
System.out.println(c.getCounters());
}
}
Running the program shows that the counters updated as expected:
$ javac Mutexes.java
$ java Mutexes
{a=20000, b=10000}
In this Java implementation, we’ve used a ReentrantLock
instead of the sync.Mutex
from Go. The ReentrantLock
provides similar functionality, allowing us to lock and unlock to ensure thread-safe access to shared resources.
We’ve also replaced Go’s goroutines with Java threads. The WaitGroup
functionality is achieved by using the join()
method on each thread, which waits for the thread to complete before moving on.
The Container
class encapsulates the shared state and the locking mechanism. The inc
method now uses a try-finally block to ensure that the lock is always released, even if an exception occurs.
In the main
method, we create and start three threads that concurrently increment the counters. We then wait for all threads to complete before printing the final state of the counters.
This example demonstrates how to use locks in Java to safely manage concurrent access to shared state across multiple threads.