Functional Programming in Java, Second Edition: All the code of Chapter 6, "Working with Resources/Managing Locks", in a single class

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.");
    }
}