All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.diversionmc.async.mutex.Mutex Maven / Gradle / Ivy

The newest version!
package net.diversionmc.async.mutex;

import net.diversionmc.async.Blocking;

import java.util.ArrayList;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

/**
 * {@link Mutex} is a thread-safe wrapper for a value with per-thread mutual exclusion. This
 * means only one thread may be accessing the stored value at a time, where others will have to
 * wait.
 *
 * 

* For this API/Implementation, {@link Mutex} associates {@link ReentrantLock} with a value, and * lets users modify the value when an {@link Mutex.Access} is held. This allows for a simple * {@link AutoCloseable} try-with-resources usage pattern via {@link #access()}, while also allowing * for the thread to access the value in a reentrant fashion without blocking itself by accident. A * functional variant of this API is also available via {@link #access(Consumer)}. * *

* This class is not based around {@link Optional}, and having a {@link Mutex} with a {@code null} * value is a common occurrence. If you want to store {@link Optional} you have to do it explicitly. * See {@link Mutex.Access#uninitialized()}. * *

* Caution * *

* Be careful when pulling a value out of the {@link Mutex} - you might be mutating underlying value * without synchronization. The exceptions are immutable values (like {@link Number}s, * {@link String}s, {@link Record record}s of immutable values, etc.) and other stored * {@link Mutex}es and similar structures, since they have their own synchronization. * *

* Examples * *

* Following example shows how to use {@link Mutex} to synchronize an {@link ArrayList} of * {@link String}s. *

{@code
 * import java.util.ArrayList;
 * import net.diversionmc.async.mutex.Mutex;
 *
 * // creating a `Mutex`
 * var mutex = Mutex.of(new ArrayList());
 *
 * // accessing the `Mutex` (from any thread)
 * try (var access = mutex.access()) { // blocking
 *     // lock happens here
 *     var list = access.get();
 *     list.add("hello");
 *     // unlock happens here
 * }
 *
 * // taking a value from a Mutex
 * var list = mutex.take(); // replaces stored value with `null`
 * assert list.equals(List.of("hello"));
 * }
* *

* This example shows how to create a simple critical section using a {@link Mutex}. *

{@code
 * import net.diversionmc.async.mutex.Mutex;
 *
 * // global
 * static Mutex criticalSection = Mutex.of();
 * static int counter = 0;
 *
 * // in all threads
 * try (var ignored = criticalSection.access()) {
 *     // critical section here
 *     // atomic read + write is guaranteed
 *     var current = counter;
 *     counter = current + 1;
 * }
 * }
* * @param Stored value type. Nullable ({@link Mutex.Access#uninitialized() uninitialized}). */ public final class Mutex { private final ReentrantLock lock = new ReentrantLock(); private T value; /** * Creates an {@link Mutex.Access#uninitialized() uninitialized} {@link Mutex}. It is highly * recommended to give out this {@link Mutex} to other users only after filling it with a value. * *

* Examples * *

{@code
     * import net.diversionmc.async.mutex.Mutex;
     *
     * var mutex = new Mutex();
     * assert mutex.uninitialized();
     * }
*/ public Mutex() { } /** * A static method alias of {@link #Mutex()} that creates an * {@link Mutex.Access#uninitialized() uninitialized} {@link Mutex}. * * @param Stored value type. * @return An {@link Mutex.Access#uninitialized() uninitialized} {@link Mutex}. */ public static Mutex of() { return new Mutex<>(); } /** * Creates a {@link Mutex} initialized to some value. * *

* Examples * *

{@code
     * import net.diversionmc.async.mutex.Mutex;
     *
     * var mutex = new Mutex<>("hello");
     * assert !mutex.uninitialized();
     * }
* * @param value Initial value. * @throws NullPointerException If {@code value} is {@code null}. Use {@link #Mutex()} instead * if you want an {@link Mutex.Access#uninitialized() * uninitialized} {@link Mutex}. */ public Mutex(T value) throws NullPointerException { Objects.requireNonNull(value, "Could not create an initialized mutex from a `null` value"); this.value = value; } /** * A static method alias of {@link #Mutex(Object)} that creates a {@link Mutex} initialized to * some value. * * @param value Initial value. * @param Stored value type. * @return A {@link Mutex} initialized to {@code value}. * @throws NullPointerException If {@code value} is {@code null}. Use {@link #Mutex()} instead * if you want an {@link Mutex.Access#uninitialized() * uninitialized} {@link Mutex}. */ public static Mutex of(T value) throws NullPointerException { return new Mutex<>(value); } /** * Access the value stored in this {@link Mutex}. Only using this method you can be certain that * the value will be accessed in a thread-safe way (so that no other thread receives a value * other than the one stored in this {@link Mutex}). * *

* Note that the {@link Mutex.Access} is {@link AutoCloseable}. This means the idiomatic way to * access the stored value is by using a try-with-resources block, in which the current thread * holds the access to the mutex. See examples below. * *

* Examples * *

{@code
     * import net.diversionmc.async.mutex.Mutex;
     *
     * var mutex = new Mutex<>("hello");
     * try (var access = mutex.access()) {
     *     var value = access.get();
     *     assert value.equals("hello");
     * }
     * }
* * @return An {@link Mutex.Access} to the value stored in this {@link Mutex}. */ @Blocking public Access access() { return new Access(); } /** * Perform an action on the value stored in this {@link Mutex}. This is a functional variant of * {@link #access()}. * *

* Note that this is a terminal access. This method returns {@code void} and there is no * way (besides {@link AtomicReference} or {@link Mutex}) to get a value out of the {@code * action} function. * *

* Examples * *

* This examples shows how to use {@link AtomicReference} to pull a value out when using this * method. *

{@code
     * import net.diversionmc.async.mutex.Mutex;
     * import java.util.concurrent.atomic.AtomicReference;
     *
     * var mutex = Mutex.of("hello");
     * var atomicValue = new AtomicReference<>();
     * mutex.access(access -> {
     *     var value = access.get();
     *     atomicValue.set(value);
     * });
     * assert atomicValue.get().equals("hello");
     * }
* * @param action Action to perform upon the value stored in this {@link Mutex}. */ @Blocking public void access(Consumer action) { try (var access = access()) { action.accept(access); } } /** * Checks if this {@link Mutex} stores a {@code null}. This indicates that the {@link Mutex} has * not yet been filled in, someone has {@link #take() took} a value from it, or the value has * been set to {@code null}. * *

* Using {@link Mutex.Access#uninitialized()} is recommended instead. * *

* Examples * *

{@code
     * import net.diversionmc.async.mutex.Mutex;
     *
     * var mutex = new Mutex<>();
     * assert mutex.uninitialized();
     * }
* * @return {@code true} if this {@link Mutex} has no stored value. * @see Mutex.Access#uninitialized() */ @Blocking public boolean uninitialized() { try (var access = access()) { return access.uninitialized(); } } /** * Take a value from this {@link Mutex}. This {@link Mutex} will be {@link #uninitialized() * uninitialized} after this operation. See {@link Mutex.Access#uninitialized()} for more * details. * * @return The value stored in this {@link Mutex}. * @see Mutex.Access#uninitialized() */ @Blocking public T take() { return set(null); } /** * Sets the value in this {@link Mutex}, returning the previously stored value. * * @param newValue The new value. * @return Previous value. May be {@code null} if {@link Mutex.Access#uninitialized()}. */ @Blocking public T set(T newValue) { try (var access = access()) { var oldValue = access.get(); access.set(newValue); return oldValue; } } /** * Checks if {@code obj} is a {@link Mutex} with the same value. Note that it does not return * {@code true} if {@code obj} is not a {@link Mutex}, even if the inner value is equal to * {@code obj}. * * @param obj The object to check. * @return {@link Object#equals(Object)} for the stored value. */ @Blocking public boolean equals(Object obj) { if (!(obj instanceof Mutex mutex2)) return false; try (var access1 = access()) { try (var access2 = mutex2.access()) { return access1.get().equals(access2.get()); } } } /** * Gets a {@link Object#hashCode()} for the stored value. * * @return {@link Object#hashCode()} for the stored value. */ @Blocking public int hashCode() { try (var access = access()) { return access.get().hashCode(); } } /** * Represents access to the {@link Mutex}'s inner value. * *

* Note that the {@link Mutex.Access} is {@link AutoCloseable}. This means the idiomatic way to * access the stored value is by using a try-with-resources block, in which the current thread * holds the access to the mutex. See examples for {@link Mutex#access()}. * * @see Mutex#access() */ public final class Access implements AutoCloseable { private boolean currentlyLocked; private Access() { lock.lock(); currentlyLocked = true; } /** * Checks if this {@link Mutex} stores a {@code null}. This indicates that the {@link Mutex} * has not yet been filled in, someone has {@link #take() took} a value from it, or the * value has been set to {@code null}. * *

* Examples * *

{@code
         * import net.diversionmc.async.mutex.Mutex;
         *
         * var mutex = new Mutex<>();
         * try (var access = mutex.access()) {
         *     assert access.uninitialized();
         * }
         * }
* * @return {@code true} if this {@link Mutex} has no stored value. * @throws IllegalStateException If this {@link Mutex.Access} no longer holds the lock. See * {@link #close()}. * @see Access#uninitialized() */ public boolean uninitialized() throws IllegalStateException { if (!currentlyLocked) throw new IllegalStateException("This access is invalid (not holding the lock)"); return value == null; } /** * Gets the value currently stored in this {@link Mutex}. * *

* Caution * *

* Be careful when pulling a value out of the {@link Mutex} - you might be mutating * underlying value without synchronization. The exceptions are immutable values (like * {@link Number}s, {@link String}s, {@link Record record}s of immutable values, etc.) and * other stored {@link Mutex}es and similar structures, since they have their own * synchronization. * *

* Examples * *

{@code
         * import net.diversionmc.async.mutex.Mutex;
         *
         * var mutex = Mutex.of("hello");
         * try (var access = mutex.access()) {
         *     var value = access.get();
         *     assert value.equals("hello");
         * }
         * }
* * @return Stored value. * @throws IllegalStateException If this {@link Mutex.Access} no longer holds the lock. See * {@link #close()}. */ public T get() throws IllegalStateException { if (!currentlyLocked) throw new IllegalStateException("This access is invalid (not holding the lock)"); return value; } /** * Stores a new value in the associated {@link Mutex}. * *

* Examples * *

{@code
         * import net.diversionmc.async.mutex.Mutex;
         *
         * var mutex = Mutex.of();
         * try (var access = mutex.access()) {
         *     access.set("hello");
         * }
         *
         * assert mutex.equals(Mutex.of("hello"));
         * }
* * @param value New value to store. * @return Previous value. May be {@code null} if {@link Mutex.Access#uninitialized()}. * @throws IllegalStateException If this {@link Mutex.Access} no longer holds the lock. See * {@link #close()}. */ public T set(T value) throws IllegalStateException { if (!currentlyLocked) throw new IllegalStateException("This access is invalid (not holding the lock)"); var oldValue = Mutex.this.value; Mutex.this.value = value; return oldValue; } /** * Closes this {@link Mutex.Access} and releases the lock on the associated {@link Mutex}. * After calling this method, this {@link Mutex.Access} can no longer be used. * *

* Examples * *

{@code
         * import net.diversionmc.async.mutex.Mutex;
         *
         * var mutex = Mutex.of();
         * var access = mutex.access();
         * access.close();
         * access.get(); // throws IllegalStateException
         * }
*/ public void close() { currentlyLocked = false; try { lock.unlock(); } catch (IllegalMonitorStateException ignored) { // safe `AutoClosable.close()` without unwanted exceptions } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy