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
commit ae28edfbddde9ed45fb0501ec6765a2af5c451f0 Author: Gary D. Gregory <[email protected]> AuthorDate: Thu Jun 19 17:31:28 2025 -0400 Add org.apache.commons.lang3.concurrent.locks.LockingVisitors.reentrantLockVisitor(Object) - Add org.apache.commons.lang3.concurrent.locks.LockingVisitors.create(Object, ReentrantLock) - Add org.apache.commons.lang3.concurrent.locks.LockingVisitors.ReentrantLockVisitor --- src/changes/changes.xml | 5 ++- .../lang3/concurrent/locks/LockingVisitors.java | 50 ++++++++++++++++++++++ .../concurrent/locks/LockingVisitorsTest.java | 23 +++++++++- 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index eac56747a..d6c6a7812 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -132,7 +132,10 @@ The <action> type attribute can be add,update,fix,remove. <action type="add" dev="ggregory" due-to="Pankraz76, Gary Gregory">Add ObjectUtils.getIfNull(Object, Object) and deprecate defaultIfNull(Object, Object).</action> <action type="add" dev="ggregory" due-to="Gary Gregory">org.apache.commons.lang3.mutable.Mutable now extends Supplier.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.CharUtils.isHex(char).</action> - <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.CharUtils.isOctal(char).</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.CharUtils.isOctal(char).</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.concurrent.locks.LockingVisitors.reentrantLockVisitor(Object).</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.concurrent.locks.LockingVisitors.create(Object, ReentrantLock).</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.lang3.concurrent.locks.LockingVisitors.ReentrantLockVisitor.</action> <!-- UPDATE --> <action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 73 to 85 #1267, #1277, #1283, #1288, #1302, #1377.</action> <action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">[site] Bump org.codehaus.mojo:taglist-maven-plugin from 3.1.0 to 3.2.1 #1300.</action> 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 65a5b985a..30b63431e 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 @@ -19,6 +19,7 @@ import java.util.Objects; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.StampedLock; import java.util.function.Supplier; @@ -325,6 +326,30 @@ protected ReadWriteLockVisitor(final O object, final ReadWriteLock readWriteLock } } + /** + * 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. + * @since 3.18.0 + */ + public static class ReentrantLockVisitor<O> extends LockVisitor<O, ReentrantLock> { + + /** + * 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#reentrantLockVisitor(Object)} instead. + * + * @param object The locked (hidden) object. The caller is supposed to drop all references to the locked object. + * @param reentrantLock the lock to use. + */ + protected ReentrantLockVisitor(final O object, final ReentrantLock reentrantLock) { + super(object, reentrantLock, () -> reentrantLock, () -> reentrantLock); + } + } + /** * 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 @@ -361,6 +386,31 @@ public static <O> ReadWriteLockVisitor<O> create(final O object, final ReadWrite return new LockingVisitors.ReadWriteLockVisitor<>(object, readWriteLock); } + /** + * Creates a new instance of {@link ReadWriteLockVisitor} with the given (hidden) object and lock. + * + * @param <O> The locked objects type. + * @param object The locked (hidden) object. + * @param reentrantLock The lock to use. + * @return The created instance, a {@link StampedLockVisitor lock} for the given object. + * @since 3.18.0 + */ + public static <O> ReentrantLockVisitor<O> create(final O object, final ReentrantLock reentrantLock) { + return new LockingVisitors.ReentrantLockVisitor<>(object, reentrantLock); + } + + /** + * 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. + * @since 3.18.0 + */ + public static <O> ReentrantLockVisitor<O> reentrantLockVisitor(final O object) { + return create(object, new ReentrantLock()); + } + /** * Creates a new instance of {@link ReadWriteLockVisitor} with the given (hidden) object. * 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 8a5fb8a89..3ce1a38d5 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 @@ -24,6 +24,7 @@ import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.LongConsumer; @@ -34,6 +35,8 @@ import org.apache.commons.lang3.concurrent.locks.LockingVisitors.StampedLockVisitor; import org.apache.commons.lang3.function.FailableConsumer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** * Tests {@link LockingVisitors}. @@ -56,7 +59,7 @@ private void runTest(final Duration delay, final boolean exclusiveLock, final Lo assertNotNull(visitor.getLock()); assertNotNull(visitor.getObject()); final boolean[] runningValues = new boolean[10]; - final long startTimeMillis = System.currentTimeMillis(); + // final long startTimeMillis = System.currentTimeMillis(); for (int i = 0; i < booleanValues.length; i++) { final int index = i; final FailableConsumer<boolean[], ?> consumer = b -> { @@ -78,7 +81,7 @@ private void runTest(final Duration delay, final boolean exclusiveLock, final Lo while (containsTrue(runningValues)) { ThreadUtils.sleep(SHORT_DELAY); } - final long endTimeMillis = System.currentTimeMillis(); + // final long endTimeMillis = System.currentTimeMillis(); for (final boolean booleanValue : booleanValues) { assertTrue(booleanValue); } @@ -126,6 +129,22 @@ void testReentrantReadWriteLockNotExclusive() throws Exception { LockingVisitors.reentrantReadWriteLockVisitor(booleanValues)); } + @Test + void testReentrantLock() throws Exception { + // If our threads are running concurrently, then we expect to be faster than running one after the other. + final boolean[] booleanValues = new boolean[10]; + runTest(DELAY, false, millis -> assertTrue(millis < TOTAL_DELAY.toMillis()), booleanValues, LockingVisitors.reentrantLockVisitor(booleanValues)); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void testReentrantLockFairness(final boolean fairness) throws Exception { + // If our threads are running concurrently, then we expect to be faster than running one after the other. + final boolean[] booleanValues = new boolean[10]; + runTest(DELAY, false, millis -> assertTrue(millis < TOTAL_DELAY.toMillis()), booleanValues, + LockingVisitors.create(booleanValues, new ReentrantLock(fairness))); + } + @Test void testResultValidation() { final Object hidden = new Object();
