diff --git a/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java b/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java index feccb423f..41164c006 100644 --- a/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java +++ b/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java @@ -21,7 +21,6 @@ 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.LongSupplier; import java.util.function.Supplier; import org.apache.commons.lang3.function.Failable; @@ -76,18 +75,26 @@ import org.apache.commons.lang3.function.FailableFunction; public class LockingVisitors { /** - * Wraps a domain object for access by lambdas. + * Wraps a domain object and a lock for access by lambdas. * * @param the wrapped object type. + * @param the wrapped lock type. */ - public abstract static class AbstractLockVisitor { - protected AbstractLockVisitor(final O object) { + public static class LockVisitor { + + private final L lock; + + private final O object; + private final Supplier readLockSupplier; + private final Supplier writeLockSupplier; + protected LockVisitor(final O object, L lock, Supplier readLockSupplier, Supplier writeLockSupplier) { super(); this.object = Objects.requireNonNull(object, "object"); + this.lock = Objects.requireNonNull(lock, "lock"); + this.readLockSupplier = Objects.requireNonNull(readLockSupplier, "readLockSupplier"); + this.writeLockSupplier = Objects.requireNonNull(writeLockSupplier, "writeLockSupplier"); } - protected final O object; - /** * Provides read (shared, non-exclusive) access to the locked (hidden) object. More precisely, what the method * will do (in the given order): @@ -104,7 +111,9 @@ public class LockingVisitors { * @see #acceptWriteLocked(FailableConsumer) * @see #applyReadLocked(FailableFunction) */ - public abstract void acceptReadLocked(FailableConsumer consumer); + public void acceptReadLocked(FailableConsumer consumer) { + lockAcceptUnlock(readLockSupplier, consumer); + } /** * Provides write (exclusive) access to the locked (hidden) object. More precisely, what the method will do (in @@ -122,7 +131,9 @@ public class LockingVisitors { * @see #acceptReadLocked(FailableConsumer) * @see #applyWriteLocked(FailableFunction) */ - public abstract void acceptWriteLocked(FailableConsumer consumer); + 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 @@ -158,7 +169,9 @@ public class LockingVisitors { * @see #acceptReadLocked(FailableConsumer) * @see #applyWriteLocked(FailableFunction) */ - public abstract T applyReadLocked(FailableFunction function); + public T applyReadLocked(FailableFunction function) { + return lockApplyUnlock(readLockSupplier, function); + } /** * Provides write (exclusive) access to the locked (hidden) object for the purpose of computing a result object. @@ -182,7 +195,77 @@ public class LockingVisitors { * @see #acceptReadLocked(FailableConsumer) * @see #applyWriteLocked(FailableFunction) */ - public abstract T applyWriteLocked(FailableFunction function); + public T applyWriteLocked(final FailableFunction function) { + return lockApplyUnlock(writeLockSupplier, function); + } + + /** + * Gets the lock. + * + * @return the lock. + */ + public L getLock() { + return lock; + } + + /** + * Gets the 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 (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 (Throwable t) { + throw Failable.rethrow(t); + } finally { + lock.unlock(); + } + } } @@ -195,8 +278,7 @@ public class LockingVisitors { * * @param The locked (hidden) objects type. */ - public static class ReadWriteLockVisitor extends AbstractLockVisitor { - private final ReadWriteLock readWriteLock; + public static class ReadWriteLockVisitor extends LockVisitor { /** * Creates a new instance with the given locked object. This constructor is supposed to be used for subclassing @@ -205,167 +287,8 @@ public class LockingVisitors { * @param object The locked (hidden) object. The caller is supposed to drop all references to the locked object. * @param readWriteLock the lock to use. */ - public ReadWriteLockVisitor(final O object, final ReadWriteLock readWriteLock) { - super(object); - this.readWriteLock = readWriteLock; - } - - /** - * 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) - */ - @Override - public void acceptReadLocked(final FailableConsumer consumer) { - lockAcceptUnlock(() -> readWriteLock.readLock(), 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) - */ - @Override - public void acceptWriteLocked(final FailableConsumer consumer) { - lockAcceptUnlock(() -> readWriteLock.writeLock(), 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: Suggest, 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) - */ - @Override - public T applyReadLocked(final FailableFunction function) { - return lockApplyUnlock(() -> readWriteLock.readLock(), 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) - */ - @Override - public T applyWriteLocked(final FailableFunction function) { - return lockApplyUnlock(() -> readWriteLock.writeLock(), function); - } - - /** - * This method provides the actual implementation for {@link #acceptReadLocked(FailableConsumer)}, and - * {@link #acceptWriteLocked(FailableConsumer)}. - * - * @param lock 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) - * @see #lockApplyUnlock(LongSupplier, FailableFunction) - */ - private void lockAcceptUnlock(final Supplier lockSupplier, final FailableConsumer consumer) { - final Lock lock = lockSupplier.get(); - try { - consumer.accept(object); - } catch (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 lock 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) - * @see #lockAcceptUnlock(LongSupplier, FailableConsumer) - */ - private T lockApplyUnlock(final Supplier lockSupplier, final FailableFunction function) { - final Lock lock = lockSupplier.get(); - try { - return function.apply(object); - } catch (Throwable t) { - throw Failable.rethrow(t); - } finally { - lock.unlock(); - } + protected ReadWriteLockVisitor(final O object, final ReadWriteLock readWriteLock) { + super(object, readWriteLock, readWriteLock::readLock, readWriteLock::writeLock); } } @@ -378,187 +301,18 @@ public class LockingVisitors { * * @param The locked (hidden) objects type. */ - public static class StampedLockVisitor extends AbstractLockVisitor { - private final StampedLock stampedLock = new StampedLock(); + 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. */ - public StampedLockVisitor(final O object) { - super(object); + protected StampedLockVisitor(final O object, StampedLock stampedLock) { + super(object, stampedLock, stampedLock::asReadLock, stampedLock::asWriteLock); } - - /** - * 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) - */ - @Override - public void acceptReadLocked(final FailableConsumer consumer) { - lockAcceptUnlock(() -> stampedLock.readLock(), 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) - */ - @Override - public void acceptWriteLocked(final FailableConsumer consumer) { - lockAcceptUnlock(() -> stampedLock.writeLock(), 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: Suggest, 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) - */ - @Override - public T applyReadLocked(final FailableFunction function) { - return lockApplyUnlock(() -> stampedLock.readLock(), 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) - */ - @Override - public T applyWriteLocked(final FailableFunction function) { - return lockApplyUnlock(() -> stampedLock.writeLock(), function); - } - - /** - * This method provides the actual implementation for {@link #acceptReadLocked(FailableConsumer)}, and - * {@link #acceptWriteLocked(FailableConsumer)}. - * - * @param stampSupplier 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) - * @see #lockApplyUnlock(LongSupplier, FailableFunction) - */ - private void lockAcceptUnlock(final LongSupplier stampSupplier, final FailableConsumer consumer) { - final long stamp = stampSupplier.getAsLong(); - try { - consumer.accept(object); - } catch (Throwable t) { - throw Failable.rethrow(t); - } finally { - stampedLock.unlock(stamp); - } - } - - /** - * 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 stampSupplier 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) - * @see #lockAcceptUnlock(LongSupplier, FailableConsumer) - */ - private T lockApplyUnlock(final LongSupplier stampSupplier, final FailableFunction function) { - final long stamp = stampSupplier.getAsLong(); - try { - return function.apply(object); - } catch (Throwable t) { - throw Failable.rethrow(t); - } finally { - stampedLock.unlock(stamp); - } - } - } - - /** - * 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); } /** @@ -572,4 +326,15 @@ public class LockingVisitors { return new LockingVisitors.ReadWriteLockVisitor<>(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()); + } + } diff --git a/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java b/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java index bd3a7ffbf..df3982ac7 100644 --- a/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java +++ b/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java @@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.function.LongConsumer; -import org.apache.commons.lang3.concurrent.locks.LockingVisitors.AbstractLockVisitor; +import org.apache.commons.lang3.concurrent.locks.LockingVisitors.LockVisitor; import org.apache.commons.lang3.concurrent.locks.LockingVisitors.StampedLockVisitor; import org.apache.commons.lang3.function.FailableConsumer; import org.junit.jupiter.api.Test; @@ -91,7 +91,7 @@ public class LockingVisitorsTest { } private void runTest(final long delayMillis, final boolean exclusiveLock, final LongConsumer runTimeCheck, - boolean[] booleanValues, AbstractLockVisitor visitor) throws InterruptedException { + boolean[] booleanValues, LockVisitor visitor) throws InterruptedException { final boolean[] runningValues = new boolean[10]; final long startTime = System.currentTimeMillis();