This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push: new ff4ef53 Redo this class after Rob Tompkins found a bug. ff4ef53 is described below commit ff4ef533a558a7a176e4649ce5c6d54fb2383217 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sat Jul 11 16:39:14 2020 -0400 Redo this class after Rob Tompkins found a bug. Much simpler now as well. --- .../lang3/concurrent/locks/LockingVisitors.java | 369 ++++----------------- .../concurrent/locks/LockingVisitorsTest.java | 4 +- 2 files changed, 69 insertions(+), 304 deletions(-) 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 feccb42..41164c0 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 <O> the wrapped object type. + * @param <L> the wrapped lock type. */ - public abstract static class AbstractLockVisitor<O extends Object> { - protected AbstractLockVisitor(final O object) { + public static class LockVisitor<O, L> { + + private final L lock; + + private final O object; + private final Supplier<Lock> readLockSupplier; + private final Supplier<Lock> writeLockSupplier; + protected LockVisitor(final O object, L lock, Supplier<Lock> readLockSupplier, Supplier<Lock> 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<O, ?> consumer); + public void acceptReadLocked(FailableConsumer<O, ?> 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<O, ?> consumer); + public void acceptWriteLocked(final FailableConsumer<O, ?> 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> T applyReadLocked(FailableFunction<O, T, ?> function); + public <T> T applyReadLocked(FailableFunction<O, T, ?> function) { + return lockApplyUnlock(readLockSupplier, function); + } /** * Provides write (exclusive) access to the locked (hidden) object for the purpose of computing a result object. @@ -182,156 +195,42 @@ public class LockingVisitors { * @see #acceptReadLocked(FailableConsumer) * @see #applyWriteLocked(FailableFunction) */ - public abstract <T> T applyWriteLocked(FailableFunction<O, T, ?> function); - - } - - /** - * 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 <O> The locked (hidden) objects type. - */ - public static class ReadWriteLockVisitor<O extends Object> extends AbstractLockVisitor<O> { - private final ReadWriteLock readWriteLock; - - /** - * 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. - */ - public ReadWriteLockVisitor(final O object, final ReadWriteLock readWriteLock) { - super(object); - this.readWriteLock = readWriteLock; + public <T> T applyWriteLocked(final FailableFunction<O, T, ?> function) { + return lockApplyUnlock(writeLockSupplier, function); } /** - * Provides read (shared, non-exclusive) access to the locked (hidden) object. More precisely, what the method - * will do (in the given order): - * <ol> - * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a - * lock is granted.</li> - * <li>Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.</li> - * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the - * lock will be released anyways.</li> - * </ol> + * Gets the lock. * - * @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) + * @return the lock. */ - @Override - public void acceptReadLocked(final FailableConsumer<O, ?> consumer) { - lockAcceptUnlock(() -> readWriteLock.readLock(), consumer); + public L getLock() { + return lock; } /** - * Provides write (exclusive) access to the locked (hidden) object. More precisely, what the method will do (in - * the given order): - * <ol> - * <li>Obtain a write (shared) lock on the locked (hidden) object. The current thread may block, until such a - * lock is granted.</li> - * <li>Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.</li> - * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the - * lock will be released anyways.</li> - * </ol> + * Gets the object. * - * @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) + * @return the object. */ - @Override - public void acceptWriteLocked(final FailableConsumer<O, ?> consumer) { - lockAcceptUnlock(() -> readWriteLock.writeLock(), consumer); + public O getObject() { + return object; } /** - * 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): - * <ol> - * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a - * lock is granted.</li> - * <li>Invokes the given {@link FailableFunction function}, passing the locked object as the parameter, - * receiving the functions result.</li> - * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the - * lock will be released anyways.</li> - * <li>Return the result object, that has been received from the functions invocation.</li> - * </ol> - * - * <em>Example:</em> 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: - * - * <pre> - * private Lock<List<Object>> listLock; - * - * public int getCurrentListSize() { - * final Integer sizeInteger = listLock.applyReadLocked((list) -> Integer.valueOf(list.size)); - * return sizeInteger.intValue(); - * } - * </pre> - * - * @param <T> 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> T applyReadLocked(final FailableFunction<O, T, ?> 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): - * <ol> - * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a - * lock is granted.</li> - * <li>Invokes the given {@link FailableFunction function}, passing the locked object as the parameter, - * receiving the functions result.</li> - * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the - * lock will be released anyways.</li> - * <li>Return the result object, that has been received from the functions invocation.</li> - * </ol> - * - * @param <T> 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> T applyWriteLocked(final FailableFunction<O, T, ?> function) { - return lockApplyUnlock(() -> readWriteLock.writeLock(), function); - } - - /** - * This method provides the actual implementation for {@link #acceptReadLocked(FailableConsumer)}, and + * This method provides the default 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 + * @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) - * @see #lockApplyUnlock(LongSupplier, FailableFunction) */ - private void lockAcceptUnlock(final Supplier<Lock> lockSupplier, final FailableConsumer<O, ?> consumer) { + protected void lockAcceptUnlock(final Supplier<Lock> lockSupplier, final FailableConsumer<O, ?> consumer) { final Lock lock = lockSupplier.get(); + lock.lock(); try { consumer.accept(object); } catch (Throwable t) { @@ -346,7 +245,7 @@ public class LockingVisitors { * {@link #applyWriteLocked(FailableFunction)}. * * @param <T> 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 + * @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. @@ -355,10 +254,10 @@ public class LockingVisitors { * 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> T lockApplyUnlock(final Supplier<Lock> lockSupplier, final FailableFunction<O, T, ?> function) { + protected <T> T lockApplyUnlock(final Supplier<Lock> lockSupplier, final FailableFunction<O, T, ?> function) { final Lock lock = lockSupplier.get(); + lock.lock(); try { return function.apply(object); } catch (Throwable t) { @@ -367,6 +266,7 @@ public class LockingVisitors { lock.unlock(); } } + } /** @@ -378,198 +278,63 @@ public class LockingVisitors { * * @param <O> The locked (hidden) objects type. */ - public static class StampedLockVisitor<O extends Object> extends AbstractLockVisitor<O> { - private final StampedLock stampedLock = new StampedLock(); + public static class ReadWriteLockVisitor<O> extends LockVisitor<O, ReadWriteLock> { /** * 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. */ - public StampedLockVisitor(final O object) { - super(object); - } - - /** - * Provides read (shared, non-exclusive) access to the locked (hidden) object. More precisely, what the method - * will do (in the given order): - * <ol> - * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a - * lock is granted.</li> - * <li>Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.</li> - * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the - * lock will be released anyways.</li> - * </ol> - * - * @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<O, ?> 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): - * <ol> - * <li>Obtain a write (shared) lock on the locked (hidden) object. The current thread may block, until such a - * lock is granted.</li> - * <li>Invokes the given {@link FailableConsumer consumer}, passing the locked object as the parameter.</li> - * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the - * lock will be released anyways.</li> - * </ol> - * - * @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<O, ?> 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): - * <ol> - * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a - * lock is granted.</li> - * <li>Invokes the given {@link FailableFunction function}, passing the locked object as the parameter, - * receiving the functions result.</li> - * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the - * lock will be released anyways.</li> - * <li>Return the result object, that has been received from the functions invocation.</li> - * </ol> - * - * <em>Example:</em> 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: - * - * <pre> - * private Lock<List<Object>> listLock; - * - * public int getCurrentListSize() { - * final Integer sizeInteger = listLock.applyReadLocked((list) -> Integer.valueOf(list.size)); - * return sizeInteger.intValue(); - * } - * </pre> - * - * @param <T> 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> T applyReadLocked(final FailableFunction<O, T, ?> 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): - * <ol> - * <li>Obtain a read (shared) lock on the locked (hidden) object. The current thread may block, until such a - * lock is granted.</li> - * <li>Invokes the given {@link FailableFunction function}, passing the locked object as the parameter, - * receiving the functions result.</li> - * <li>Release the lock, as soon as the consumers invocation is done. If the invocation results in an error, the - * lock will be released anyways.</li> - * <li>Return the result object, that has been received from the functions invocation.</li> - * </ol> - * - * @param <T> 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> T applyWriteLocked(final FailableFunction<O, T, ?> function) { - return lockApplyUnlock(() -> stampedLock.writeLock(), function); + protected ReadWriteLockVisitor(final O object, final ReadWriteLock readWriteLock) { + super(object, readWriteLock, readWriteLock::readLock, readWriteLock::writeLock); } + } - /** - * 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<O, ?> consumer) { - final long stamp = stampSupplier.getAsLong(); - try { - consumer.accept(object); - } catch (Throwable t) { - throw Failable.rethrow(t); - } finally { - stampedLock.unlock(stamp); - } - } + /** + * 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 <O> The locked (hidden) objects type. + */ + public static class StampedLockVisitor<O> extends LockVisitor<O, StampedLock> { /** - * This method provides the actual implementation for {@link #applyReadLocked(FailableFunction)}, and - * {@link #applyWriteLocked(FailableFunction)}. + * 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 <T> 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) + * @param object The locked (hidden) object. The caller is supposed to drop all references to the locked object. + * @param stampedLock the lock to use. */ - private <T> T lockApplyUnlock(final LongSupplier stampSupplier, final FailableFunction<O, T, ?> function) { - final long stamp = stampSupplier.getAsLong(); - try { - return function.apply(object); - } catch (Throwable t) { - throw Failable.rethrow(t); - } finally { - stampedLock.unlock(stamp); - } + protected StampedLockVisitor(final O object, StampedLock stampedLock) { + super(object, stampedLock, stampedLock::asReadLock, stampedLock::asWriteLock); } } /** - * Creates a new instance of {@link StampedLockVisitor} with the given (hidden) object. + * Creates a new instance of {@link ReadWriteLockVisitor} with the given (hidden) object. * * @param <O> The locked objects type. * @param object The locked (hidden) object. * @return The created instance, a {@link StampedLockVisitor lock} for the given object. */ - public static <O> StampedLockVisitor<O> stampedLockVisitor(final O object) { - return new LockingVisitors.StampedLockVisitor<>(object); + public static <O> ReadWriteLockVisitor<O> reentrantReadWriteLockVisitor(final O object) { + return new LockingVisitors.ReadWriteLockVisitor<>(object, new ReentrantReadWriteLock()); } /** - * Creates a new instance of {@link ReadWriteLockVisitor} with the given (hidden) object. + * Creates a new instance of {@link StampedLockVisitor} with the given (hidden) object. * * @param <O> The locked objects type. * @param object The locked (hidden) object. * @return The created instance, a {@link StampedLockVisitor lock} for the given object. */ - public static <O> ReadWriteLockVisitor<O> reentrantReadWriteLockVisitor(final O object) { - return new LockingVisitors.ReadWriteLockVisitor<>(object, new ReentrantReadWriteLock()); + public static <O> StampedLockVisitor<O> 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 bd3a7ff..df3982a 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<boolean[]> visitor) throws InterruptedException { + boolean[] booleanValues, LockVisitor<boolean[], ?> visitor) throws InterruptedException { final boolean[] runningValues = new boolean[10]; final long startTime = System.currentTimeMillis();