This is the code of the book with some reorganization and extra code trying different approaches.
package chapter6;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.IntStream;
// See also:
// https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/concurrent/locks/Lock.html
// https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html
// ---
// Critical section code enclosed in a record carrying a name.
// This more func than just printing something in a critical section.
// ---
record CriticalSectionCode(String name) implements Runnable {
public void run() {
System.out.println(name() + " in critical section");
try {
if (Math.random() > 0.90) {
System.out.println(name() + " interrupting every thread in the thread group!");
Thread.currentThread().getThreadGroup().interrupt();
} else {
final long sleep_ms = 500 + (int) (Math.random() * 1000);
System.out.print(name() + " will be staying in critical section for the next " + sleep_ms + " ms");
System.out.println(" (" + Thread.currentThread().getThreadGroup().activeCount() + " active threads in thread group)");
Thread.sleep(sleep_ms);
}
} catch (InterruptedException ex) {
System.out.println(name() + " was interrupted");
// Someone told this thread to stop sleeping!
// Set the "interrupted" flag and get out.
Thread.currentThread().interrupt();
}
}
}
// ---
// "resources/fpij/Locking.java" on p.115
// ---
class SimpleLocker {
private final Lock lock = new ReentrantLock(); // or mock
// There is a setLock() but why would one ever set the lock?
/*
protected void setLock(final Lock mock) {
lock = mock;
}
*/
public void runCriticalSectionInsideLock(final String name) {
lock.lock(); // hardcoded
try {
(new CriticalSectionCode(name)).run(); // hardcoded
} finally {
lock.unlock();
}
}
}
// ---
// "resources/fpij/Locker.java" p.115
// Uses the "Execute Around Method Pattern".
// But should this really be a class rather than a simple function?
// ---
class EamLocker implements Function<Lock, Consumer<Runnable>> {
@Override
public Consumer<Runnable> apply(final Lock lock) {
return (Runnable criticalSection) -> {
lock.lock();
try {
criticalSection.run();
} finally {
lock.unlock();
}
};
}
}
// ============================
public class WorkingWithResources_ManagingLocks {
private final static int numThreads = 10;
// Helper
private static void waitForAllThreadsToEnd(List<Thread> threads) {
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
// Joining the thread was interrupted.
// So I guess we can move on to the next join?
}
});
}
// ---
// Using the simple locker with the Thread creation in a separate method for clarity
// ---
private static Thread makeThread(final SimpleLocker locker, final int i) {
return new Thread(() -> locker.runCriticalSectionInsideLock("thread " + i));
}
@Test
void usingTheSimpleLocker() {
// SimpleLocker has an internal lock instance
final SimpleLocker locker = new SimpleLocker();
List<Thread> threads = IntStream.range(0, numThreads).mapToObj(i -> makeThread(locker, i)).toList();
threads.forEach(Thread::start);
waitForAllThreadsToEnd(threads);
System.out.println("All threads ended.");
}
// ---
// Using the lock-around maker with the Thread creation in a separate method for clarity,
// passing the CriticalSectionCode instance.
// ---
private static Thread makeThread(final Consumer<Runnable> lockAround, final CriticalSectionCode csc) {
return new Thread(() -> lockAround.accept(csc));
}
@Test
void usingTheLockAroundMaker() {
// Here we provide the lock
final Lock lock = new ReentrantLock();
final Consumer<Runnable> lockAround = (new EamLocker()).apply(lock);
List<Thread> threads =
IntStream.range(0, numThreads)
.mapToObj(i -> {
CriticalSectionCode csc = new CriticalSectionCode("thread " + i);
return makeThread(lockAround, csc);
})
.toList();
threads.forEach(Thread::start);
waitForAllThreadsToEnd(threads);
System.out.println("All threads ended.");
}
// ---
// An alternative with a BiFunction instead of EamLocker instance
// ---
private static Runnable makeRunnable(final Lock lock, final Runnable criticalSection) {
return () -> {
lock.lock();
try {
criticalSection.run();
} finally {
lock.unlock();
}
};
}
@Test
void usingBuildRunnableFunction() {
final Lock lock = new ReentrantLock();
final Consumer<Runnable> lockAround = (new EamLocker()).apply(lock);
List<Thread> threads = IntStream.range(0, numThreads).mapToObj(i -> {
CriticalSectionCode csc = new CriticalSectionCode("thread " + i);
return new Thread(makeRunnable(lock, csc));
}).toList();
threads.forEach(Thread::start);
waitForAllThreadsToEnd(threads);
System.out.println("All threads ended.");
}
// ---
// An alternative with a curried function instead of EamLocker instance
// ---
private static Function<Runnable, Runnable> makeRunnableMaker(final Lock lock) {
return (final Runnable criticalSection) -> () -> {
lock.lock();
try {
criticalSection.run();
} finally {
lock.unlock();
}
};
}
@Test
void usingTheLockAroundMaker3() {
final Lock lock = new ReentrantLock();
final Consumer<Runnable> lockAround = (new EamLocker()).apply(lock);
List<Thread> threads =
IntStream.range(0, numThreads)
.mapToObj(i -> {
CriticalSectionCode csc = new CriticalSectionCode("thread " + i);
return new Thread(makeRunnableMaker(lock).apply(csc));
}).toList();
threads.forEach(Thread::start);
waitForAllThreadsToEnd(threads);
System.out.println("All threads ended.");
}
}