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

org.apache.commons.lang3.concurrent.locks.LockingVisitors Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.lang3.concurrent.locks;

import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Supplier;

import org.apache.commons.lang3.function.Failable;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableFunction;

/**
 * Combines the monitor and visitor pattern to work with {@link java.util.concurrent.locks.Lock locked objects}. Locked
 * objects are an alternative to synchronization. This, on Wikipedia, is known as the Visitor pattern
 * (https://en.wikipedia.org/wiki/Visitor_pattern), and from the "Gang of Four" "Design Patterns" book's Visitor pattern
 * [Gamma, E., Helm, R., & Johnson, R. (1998). Visitor. In Design patterns elements of reusable object oriented software (pp. 331-344). Reading: Addison Wesley.].
 *
 * 

* Locking is preferable, if there is a distinction between read access (multiple threads may have read access * concurrently), and write access (only one thread may have write access at any given time). In comparison, * synchronization doesn't support read access, because synchronized access is exclusive. *

*

* Using this class is fairly straightforward: *

*
    *
  1. While still in single thread mode, create an instance of {@link LockingVisitors.StampedLockVisitor} by calling * {@link #stampedLockVisitor(Object)}, passing the object which needs to be locked. Discard all references to the * locked object. Instead, use references to the lock.
  2. *
  3. If you want to access the locked object, create a {@link FailableConsumer}. The consumer will receive the locked * object as a parameter. For convenience, the consumer may be implemented as a Lambda. Then invoke * {@link LockingVisitors.StampedLockVisitor#acceptReadLocked(FailableConsumer)}, or * {@link LockingVisitors.StampedLockVisitor#acceptWriteLocked(FailableConsumer)}, passing the consumer.
  4. *
  5. As an alternative, if you need to produce a result object, you may use a {@link FailableFunction}. This function * may also be implemented as a Lambda. To have the function executed, invoke * {@link LockingVisitors.StampedLockVisitor#applyReadLocked(FailableFunction)}, or * {@link LockingVisitors.StampedLockVisitor#applyWriteLocked(FailableFunction)}.
  6. *
*

* Example: A thread safe logger class. *

* *
 *   public class SimpleLogger {
 *
 *     private final StampedLockVisitor<PrintStream> lock;
 *
 *     public SimpleLogger(OutputStream out) {
 *         lock = LockingVisitors.stampedLockVisitor(new PrintStream(out));
 *     }
 *
 *     public void log(String message) {
 *         lock.acceptWriteLocked((ps) -> ps.println(message));
 *     }
 *
 *     public void log(byte[] buffer) {
 *         lock.acceptWriteLocked((ps) -> { ps.write(buffer); ps.println(); });
 *     }
 * 
* * @since 3.11 */ public class LockingVisitors { /** * Wraps a domain object and a lock for access by lambdas. * * @param the wrapped object type. * @param the wrapped lock type. */ public static class LockVisitor { /** * The lock object, untyped, since, for example {@link StampedLock} does not implement a locking interface in * Java 8. */ private final L lock; /** * The guarded object. */ private final O object; /** * Supplies the read lock, usually from the lock object. */ private final Supplier readLockSupplier; /** * Supplies the write lock, usually from the lock object. */ private final Supplier writeLockSupplier; /** * Constructs an instance. * * @param object The object to guard. * @param lock The locking object. * @param readLockSupplier Supplies the read lock, usually from the lock object. * @param writeLockSupplier Supplies the write lock, usually from the lock object. */ protected LockVisitor(final O object, final L lock, final Supplier readLockSupplier, final Supplier writeLockSupplier) { this.object = Objects.requireNonNull(object, "object"); this.lock = Objects.requireNonNull(lock, "lock"); this.readLockSupplier = Objects.requireNonNull(readLockSupplier, "readLockSupplier"); this.writeLockSupplier = Objects.requireNonNull(writeLockSupplier, "writeLockSupplier"); } /** * Provides read (shared, non-exclusive) access to the locked (hidden) object. More precisely, what the method * will do (in the given order): * *
    *
  1. Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a * lock is granted.
  2. *
  3. Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.
  4. *
  5. Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the * lock will be released anyways.
  6. *
* * @param consumer The consumer, which is being invoked to use the hidden object, which will be passed as the * consumers parameter. * @see #acceptWriteLocked(FailableConsumer) * @see #applyReadLocked(FailableFunction) */ public void acceptReadLocked(final FailableConsumer consumer) { lockAcceptUnlock(readLockSupplier, consumer); } /** * Provides write (exclusive) access to the locked (hidden) object. More precisely, what the method will do (in * the given order): * *
    *
  1. Obtain a write (shared) lock on the locked (hidden) object. The current thread may block, until such a * lock is granted.
  2. *
  3. Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.
  4. *
  5. Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the * lock will be released anyways.
  6. *
* * @param consumer The consumer, which is being invoked to use the hidden object, which will be passed as the * consumers parameter. * @see #acceptReadLocked(FailableConsumer) * @see #applyWriteLocked(FailableFunction) */ public void acceptWriteLocked(final FailableConsumer consumer) { lockAcceptUnlock(writeLockSupplier, consumer); } /** * Provides read (shared, non-exclusive) access to the locked (hidden) object for the purpose of computing a * result object. More precisely, what the method will do (in the given order): * *
    *
  1. Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a * lock is granted.
  2. *
  3. Invokes the given {@link FailableFunction function}, passing the locked object as the parameter, * receiving the functions result.
  4. *
  5. Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the * lock will be released anyways.
  6. *
  7. Return the result object, that has been received from the functions invocation.
  8. *
*

* Example: Consider that the hidden object is a list, and we wish to know the current size of the * list. This might be achieved with the following: *

*
         * private Lock<List<Object>> listLock;
         *
         * public int getCurrentListSize() {
         *     final Integer sizeInteger = listLock.applyReadLocked((list) -> Integer.valueOf(list.size));
         *     return sizeInteger.intValue();
         * }
         * 
* * @param The result type (both the functions, and this method's.) * @param function The function, which is being invoked to compute the result. The function will receive the * hidden object. * @return The result object, which has been returned by the functions invocation. * @throws IllegalStateException The result object would be, in fact, the hidden object. This would extend * access to the hidden object beyond this methods lifetime and will therefore be prevented. * @see #acceptReadLocked(FailableConsumer) * @see #applyWriteLocked(FailableFunction) */ public T applyReadLocked(final FailableFunction function) { return lockApplyUnlock(readLockSupplier, function); } /** * Provides write (exclusive) access to the locked (hidden) object for the purpose of computing a result object. * More precisely, what the method will do (in the given order): * *
    *
  1. Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a * lock is granted.
  2. *
  3. Invokes the given {@link FailableFunction function}, passing the locked object as the parameter, * receiving the functions result.
  4. *
  5. Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the * lock will be released anyways.
  6. *
  7. Return the result object, that has been received from the functions invocation.
  8. *
* * @param The result type (both the functions, and this method's.) * @param function The function, which is being invoked to compute the result. The function will receive the * hidden object. * @return The result object, which has been returned by the functions invocation. * @throws IllegalStateException The result object would be, in fact, the hidden object. This would extend * access to the hidden object beyond this methods lifetime and will therefore be prevented. * @see #acceptReadLocked(FailableConsumer) * @see #applyWriteLocked(FailableFunction) */ public T applyWriteLocked(final FailableFunction function) { return lockApplyUnlock(writeLockSupplier, function); } /** * Gets the lock. * * @return the lock. */ public L getLock() { return lock; } /** * Gets the guarded object. * * @return the object. */ public O getObject() { return object; } /** * This method provides the default implementation for {@link #acceptReadLocked(FailableConsumer)}, and * {@link #acceptWriteLocked(FailableConsumer)}. * * @param lockSupplier A supplier for the lock. (This provides, in fact, a long, because a {@link StampedLock} is used * internally.) * @param consumer The consumer, which is to be given access to the locked (hidden) object, which will be passed * as a parameter. * @see #acceptReadLocked(FailableConsumer) * @see #acceptWriteLocked(FailableConsumer) */ protected void lockAcceptUnlock(final Supplier lockSupplier, final FailableConsumer consumer) { final Lock lock = lockSupplier.get(); lock.lock(); try { consumer.accept(object); } catch (final Throwable t) { throw Failable.rethrow(t); } finally { lock.unlock(); } } /** * This method provides the actual implementation for {@link #applyReadLocked(FailableFunction)}, and * {@link #applyWriteLocked(FailableFunction)}. * * @param The result type (both the functions, and this method's.) * @param lockSupplier A supplier for the lock. (This provides, in fact, a long, because a {@link StampedLock} is used * internally.) * @param function The function, which is being invoked to compute the result object. This function will receive * the locked (hidden) object as a parameter. * @return The result object, which has been returned by the functions invocation. * @throws IllegalStateException The result object would be, in fact, the hidden object. This would extend * access to the hidden object beyond this methods lifetime and will therefore be prevented. * @see #applyReadLocked(FailableFunction) * @see #applyWriteLocked(FailableFunction) */ protected T lockApplyUnlock(final Supplier lockSupplier, final FailableFunction function) { final Lock lock = lockSupplier.get(); lock.lock(); try { return function.apply(object); } catch (final Throwable t) { throw Failable.rethrow(t); } finally { lock.unlock(); } } } /** * This class implements a wrapper for a locked (hidden) object, and provides the means to access it. The basic * idea, is that the user code forsakes all references to the locked object, using only the wrapper object, and the * accessor methods {@link #acceptReadLocked(FailableConsumer)}, {@link #acceptWriteLocked(FailableConsumer)}, * {@link #applyReadLocked(FailableFunction)}, and {@link #applyWriteLocked(FailableFunction)}. By doing so, the * necessary protections are guaranteed. * * @param The locked (hidden) objects type. */ public static class ReadWriteLockVisitor extends LockVisitor { /** * Creates a new instance with the given locked object. This constructor is supposed to be used for subclassing * only. In general, it is suggested to use {@link LockingVisitors#stampedLockVisitor(Object)} instead. * * @param object The locked (hidden) object. The caller is supposed to drop all references to the locked object. * @param readWriteLock the lock to use. */ protected ReadWriteLockVisitor(final O object, final ReadWriteLock readWriteLock) { super(object, readWriteLock, readWriteLock::readLock, readWriteLock::writeLock); } } /** * This class implements a wrapper for a locked (hidden) object, and provides the means to access it. The basic * idea is that the user code forsakes all references to the locked object, using only the wrapper object, and the * accessor methods {@link #acceptReadLocked(FailableConsumer)}, {@link #acceptWriteLocked(FailableConsumer)}, * {@link #applyReadLocked(FailableFunction)}, and {@link #applyWriteLocked(FailableFunction)}. By doing so, the * necessary protections are guaranteed. * * @param The locked (hidden) objects type. */ public static class StampedLockVisitor extends LockVisitor { /** * Creates a new instance with the given locked object. This constructor is supposed to be used for subclassing * only. In general, it is suggested to use {@link LockingVisitors#stampedLockVisitor(Object)} instead. * * @param object The locked (hidden) object. The caller is supposed to drop all references to the locked object. * @param stampedLock the lock to use. */ protected StampedLockVisitor(final O object, final StampedLock stampedLock) { super(object, stampedLock, stampedLock::asReadLock, stampedLock::asWriteLock); } } /** * Creates a new instance of {@link ReadWriteLockVisitor} with the given (hidden) object and lock. * * @param The locked objects type. * @param object The locked (hidden) object. * @param readWriteLock The lock to use. * @return The created instance, a {@link StampedLockVisitor lock} for the given object. * @since 3.13.0 */ public static ReadWriteLockVisitor create(final O object, final ReadWriteLock readWriteLock) { return new LockingVisitors.ReadWriteLockVisitor<>(object, readWriteLock); } /** * Creates a new instance of {@link ReadWriteLockVisitor} with the given (hidden) object. * * @param The locked objects type. * @param object The locked (hidden) object. * @return The created instance, a {@link StampedLockVisitor lock} for the given object. */ public static ReadWriteLockVisitor reentrantReadWriteLockVisitor(final O object) { return create(object, new ReentrantReadWriteLock()); } /** * Creates a new instance of {@link StampedLockVisitor} with the given (hidden) object. * * @param The locked objects type. * @param object The locked (hidden) object. * @return The created instance, a {@link StampedLockVisitor lock} for the given object. */ public static StampedLockVisitor stampedLockVisitor(final O object) { return new LockingVisitors.StampedLockVisitor<>(object, new StampedLock()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy