This is an automated email from the ASF dual-hosted git repository.

jochen 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 c141bc9  Adding the Locks class.
c141bc9 is described below

commit c141bc961cb5cb84c47a21c3a5b0f15ab0035728
Author: Jochen Wiedmann <joc...@apache.org>
AuthorDate: Thu May 28 22:52:46 2020 +0200

    Adding the Locks class.
---
 src/changes/changes.xml                            |   5 +-
 src/main/java/org/apache/commons/lang3/Locks.java  | 116 +++++++++++++++++++++
 .../java/org/apache/commons/lang3/LocksTest.java   |  73 +++++++++++++
 3 files changed, 192 insertions(+), 2 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 1df43f2..2204f32 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -46,7 +46,7 @@ The <action> type attribute can be add,update,fix,remove.
   <body>
 
   <release version="3.11" date="2020-MM-DD" description="New features and bug 
fixes..">
-    <action                   type="fix" dev="kinow" 
due-to="contextshuffling">Fix Javdoc for 
StringUtils.appendIfMissingIgnoreCase() #507.</action>
+    <action                   type="fix" dev="kinow" 
due-to="contextshuffling">Fix Javadoc for 
StringUtils.appendIfMissingIgnoreCase() #507.</action>
     <action                   type="update" 
dev="ggregory">org.junit-pioneer:junit-pioneer 0.5.4 -> 0.6.0.</action>
     <action                   type="update" 
dev="ggregory">org.junit.jupiter:junit-jupiter 5.6.0 -> 5.6.1.</action>
     <action                   type="update" 
dev="ggregory">com.github.spotbugs:spotbugs 4.0.0 -> 4.0.3.</action>
@@ -59,6 +59,7 @@ The <action> type attribute can be add,update,fix,remove.
     <action                   type="update" dev="ggregory" due-to="Arend v. 
Reinersdorff, Bruno P. Kinoshita">(Javadoc) Fix return tag for throwableOf*() 
methods #518.</action>
     <action                   type="add" dev="ggregory" due-to="XenoAmess, 
Gary Gregory">Add ArrayUtils.isSameLength() to compare more array types 
#430.</action>
     <action issue="LANG-1545" type="update" dev="ggregory" due-to="XenoAmess, 
Gary Gregory">CharSequenceUtils.regionMatches is wrong dealing with 
Georgian.</action>
+    <action                   type="add" dev="jochen">Added the Locks class as 
a convenient possibility to deal with locked objects.</action>
   </release>
 
   <release version="3.10" date="2020-03-22" description="New features and bug 
fixes. Requires Java 8, supports Java 9, 10, 11.">
@@ -275,7 +276,7 @@ The <action> type attribute can be add,update,fix,remove.
     <action issue="LANG-1195" type="add" dev="pschumacher" due-to="Derek C. 
Ashmore">Enhance MethodUtils to allow invocation of private methods</action>
     <action issue="LANG-1199" type="fix" dev="pschumacher" due-to="M. 
Steiger">Fix implementation of StringUtils.getJaroWinklerDistance()</action>
     <action issue="LANG-1244" type="fix" dev="pschumacher" 
due-to="jjbankert">Fix dead links in StringUtils.getLevenshteinDistance() 
javadoc</action>
-    <action issue="LANG-1242" type="fix" dev="pschumacher" due-to="Neal 
Stewart">"\u2284":"&nsub;" mapping missing from 
EntityArrays#HTML40_EXTENDED_ESCAPE</action>
+    <action issue="LANG-1242" type="fix" dev="pschumacher" due-to="Neal 
Stewart">"\u2284":"nsub" mapping missing from 
EntityArrays#HTML40_EXTENDED_ESCAPE</action>
     <action issue="LANG-1243" type="update" dev="sebb">Simplify ArrayUtils 
removeElements by using new decrementAndGet() method</action>
     <action issue="LANG-1189" type="add" dev="sebb" due-to="haiyang li / 
Matthew Bartenschlag ">Add 
getAndIncrement/getAndDecrement/getAndAdd/incrementAndGet/decrementAndGet/addAndGet
 in Mutable* classes</action>
     <action issue="LANG-1240" type="update" dev="pschumacher" 
due-to="zhanhb">Optimize BitField constructor implementation</action>
diff --git a/src/main/java/org/apache/commons/lang3/Locks.java 
b/src/main/java/org/apache/commons/lang3/Locks.java
new file mode 100644
index 0000000..60b4f6a
--- /dev/null
+++ b/src/main/java/org/apache/commons/lang3/Locks.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import java.util.Objects;
+import java.util.concurrent.locks.StampedLock;
+
+import org.apache.commons.lang3.Functions.FailableConsumer;
+import org.apache.commons.lang3.Functions.FailableFunction;
+
+
+/** Utility class for working with {@link java.util.concurrent.locks.Lock 
locked objects}. Locked objects are an
+ * alternative to synchronization.
+ * 
+ * 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:
+ * <ol>
+ *   <li>While still in single thread mode, create an instance of {@link 
Locks.Lock} by calling
+ *     {@link #lock(Object)}, passing the object, which needs to be locked. 
Discard all
+ *     references to the locked object. Instead, use references to the 
lock.</li>
+ *   <li>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 Lamba. Then invoke {@link 
Locks.Lock#runReadLocked(FailableConsumer)},
+ *     or {@link Locks.Lock#runWriteLocked(FailableConsumer)}, passing the 
consumer.</li>
+ *   <li>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 
Locks.Lock#callReadLocked(FailableFunction)}, or
+ *     {@link Locks.Lock#callWriteLocked(FailableFunction)}.</li>
+ * </ol>
+ *
+ * Example: A thread safe logger class.
+ * <pre>
+ *   public class SimpleLogger {
+ *     private final Lock&lt;PrintStream&gt; lock;
+ *
+ *     public SimpleLogger(OutputStream out) {
+ *         PrintStream ps = new Printstream(out);
+ *         lock = Locks.lock(ps);
+ *     }
+ * 
+ *     public void log(String message) {
+ *         lock.runWriteLocked((ps) -&gt; ps.println(message));
+ *     }
+ *
+ *     public void log(byte[] buffer) {
+ *         lock.runWriteLocked((ps) -&gt; { ps.write(buffer); ps.println(); });
+ *     }
+ * </pre> 
+ */
+public class Locks {
+       public static class Lock<O extends Object> {
+               private final O lockedObject;
+               private final StampedLock lock = new StampedLock();
+
+               public Lock(O lockedObject) {
+                       this.lockedObject = 
Objects.requireNonNull(lockedObject, "Locked Object");
+               }
+
+               public void runReadLocked(FailableConsumer<O,?> consumer) {
+                       runLocked(lock.readLock(), consumer);
+               }
+
+               public void runWriteLocked(FailableConsumer<O,?> consumer) {
+                       runLocked(lock.writeLock(), consumer);
+               }
+
+               public <T> T callReadLocked(FailableFunction<O,T,?> function) {
+                       return callLocked(lock.readLock(), function);
+               }
+
+               public <T> T callWriteLocked(FailableFunction<O,T,?> function) {
+                       return callLocked(lock.writeLock(), function);
+               }
+
+               protected void runLocked(long stamp, FailableConsumer<O,?> 
consumer) {
+                       try {
+                               consumer.accept(lockedObject);
+                       } catch (Throwable t) {
+                               throw Functions.rethrow(t);
+                       } finally {
+                               lock.unlock(stamp);
+                       }
+               }
+
+               protected <T> T callLocked(long stamp, FailableFunction<O,T,?> 
function) {
+                       try {
+                               return function.apply(lockedObject);
+                       } catch (Throwable t) {
+                               throw Functions.rethrow(t);
+                       } finally {
+                               lock.unlock(stamp);
+                       }
+               }
+       }
+
+       public static <O extends Object> Locks.Lock<O> lock(O object) {
+               return new Locks.Lock<O>(object);
+       }
+}
diff --git a/src/test/java/org/apache/commons/lang3/LocksTest.java 
b/src/test/java/org/apache/commons/lang3/LocksTest.java
new file mode 100644
index 0000000..e2dd6b0
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/LocksTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.commons.lang3.Functions.FailableConsumer;
+import org.apache.commons.lang3.Locks.Lock;
+import org.junit.jupiter.api.Test;
+
+class LocksTest {
+       @Test
+       void testReadLock() throws Exception {
+               final long DELAY=3000;
+               final boolean[] booleanValues = new boolean[10];
+               final Lock<boolean[]> lock = Locks.lock(booleanValues);
+               final boolean[] runningValues = new boolean[10];
+
+               final long startTime = System.currentTimeMillis();
+               for (int i = 0;  i < booleanValues.length;  i++) {
+                       final int index = i;
+                       final FailableConsumer<boolean[],?> consumer = (b) -> {
+                               b[index] = false;
+                               Thread.sleep(DELAY);
+                               b[index] = true;
+                               modify(runningValues, index, false);
+                       };
+                       final Thread t = new Thread(() -> 
lock.runReadLocked(consumer));
+                       modify(runningValues, i, true);
+                       t.start();
+               }
+               while (someValueIsTrue(runningValues)) {
+                       Thread.sleep(100);
+               }
+               final long endTime = System.currentTimeMillis();
+               for (int i = 0;  i < booleanValues.length;  i++) {
+                       assertTrue(booleanValues[i]);
+               }
+               // If our threads would be running in exclusive mode, then we'd 
need
+               // at least DELAY milliseconds for each.
+               assertTrue((endTime-startTime) < booleanValues.length*DELAY);
+       }
+
+       protected void modify(boolean[] booleanArray, int offset, boolean 
value) {
+               synchronized(booleanArray) {
+                       booleanArray[offset] = value;
+               }
+       }
+       protected boolean someValueIsTrue(boolean[] booleanArray) {
+               synchronized(booleanArray) {
+                       for (int i = 0;  i < booleanArray.length;  i++) {
+                               if (booleanArray[i]) {
+                                       return true;
+                               }
+                       }
+                       return false;
+               }
+       }
+}

Reply via email to