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();

Reply via email to