[LANG-1275] Added a tryAcquire() method to TimedSemaphore.

This method supports non-blocking use cases.


Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/809e2bed
Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/809e2bed
Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/809e2bed

Branch: refs/heads/release
Commit: 809e2bed22f02264e0ac58bba070f6d84386cfc5
Parents: 496506d
Author: oheger <oliver.he...@oliver-heger.de>
Authored: Sat Oct 8 16:47:10 2016 +0200
Committer: oheger <oliver.he...@oliver-heger.de>
Committed: Sat Oct 8 16:47:10 2016 +0200

----------------------------------------------------------------------
 .../lang3/concurrent/TimedSemaphore.java        | 77 ++++++++++++++++----
 .../lang3/concurrent/TimedSemaphoreTest.java    | 67 +++++++++++++++++
 2 files changed, 129 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-lang/blob/809e2bed/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java 
b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
index 9e47b16..a3517bd 100644
--- a/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
+++ b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
@@ -16,13 +16,13 @@
  */
 package org.apache.commons.lang3.concurrent;
 
+import org.apache.commons.lang3.Validate;
+
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.commons.lang3.Validate;
-
 /**
  * <p>
  * A specialized <em>semaphore</em> implementation that provides a number of
@@ -99,13 +99,22 @@ import org.apache.commons.lang3.Validate;
  * </p>
  * <p>
  * Client code that uses {@code TimedSemaphore} has to call the
- * {@link #acquire()} method in aach processing step. {@code TimedSemaphore}
+ * {@link #acquire()} method in each processing step. {@code TimedSemaphore}
  * keeps track of the number of invocations of the {@link #acquire()} method 
and
  * blocks the calling thread if the counter exceeds the limit specified. When
  * the timer signals the end of the time period the counter is reset and all
  * waiting threads are released. Then another cycle can start.
  * </p>
  * <p>
+ * An alternative to {@code acquire()} is the {@link #tryAcquire()} method. 
This
+ * method checks whether the semaphore is under the specified limit and
+ * increases the internal counter if this is the case. The return value is then
+ * <strong>true</strong>, and the calling thread can continue with its action.
+ * If the semaphore is already at its limit, {@code tryAcquire()} immediately
+ * returns <strong>false</strong> without blocking; the calling thread must
+ * then abort its action. This usage scenario prevents blocking of threads.
+ * </p>
+ * <p>
  * It is possible to modify the limit at any time using the
  * {@link #setLimit(int)} method. This is useful if the load produced by an
  * operation has to be adapted dynamically. In the example scenario with the
@@ -280,7 +289,7 @@ public class TimedSemaphore {
     }
 
     /**
-     * Tries to acquire a permit from this semaphore. This method will block if
+     * Acquires a permit from this semaphore. This method will block if
      * the limit for the current period has already been reached. If
      * {@link #shutdown()} has already been invoked, calling this method will
      * cause an exception. The very first call of this method starts the timer
@@ -291,26 +300,34 @@ public class TimedSemaphore {
      * @throws IllegalStateException if this semaphore is already shut down
      */
     public synchronized void acquire() throws InterruptedException {
-        if (isShutdown()) {
-            throw new IllegalStateException("TimedSemaphore is shut down!");
-        }
+        prepareAcquire();
 
-        if (task == null) {
-            task = startTimer();
-        }
-
-        boolean canPass = false;
+        boolean canPass;
         do {
-            canPass = getLimit() <= NO_LIMIT || acquireCount < getLimit();
+            canPass = acquirePermit();
             if (!canPass) {
                 wait();
-            } else {
-                acquireCount++;
             }
         } while (!canPass);
     }
 
     /**
+     * Tries to acquire a permit from this semaphore. If the limit of this 
semaphore has
+     * not yet been reached, a permit is acquired, and this method returns
+     * <strong>true</strong>. Otherwise, this method returns immediately with 
the result
+     * <strong>false</strong>.
+     *
+     * @return <strong>true</strong> if a permit could be acquired; 
<strong>false</strong>
+     * otherwise
+     * @throws IllegalStateException if this semaphore is already shut down
+     * @since 3.5
+     */
+    public synchronized boolean tryAcquire() {
+        prepareAcquire();
+        return acquirePermit();
+    }
+
+    /**
      * Returns the number of (successful) acquire invocations during the last
      * period. This is the number of times the {@link #acquire()} method was
      * called without blocking. This can be useful for testing or debugging
@@ -420,4 +437,34 @@ public class TimedSemaphore {
         acquireCount = 0;
         notifyAll();
     }
+
+    /**
+     * Prepares an acquire operation. Checks for the current state and starts 
the internal
+     * timer if necessary. This method must be called with the lock of this 
object held.
+     */
+    private void prepareAcquire() {
+        if (isShutdown()) {
+            throw new IllegalStateException("TimedSemaphore is shut down!");
+        }
+
+        if (task == null) {
+            task = startTimer();
+        }
+    }
+
+    /**
+     * Internal helper method for acquiring a permit. This method checks 
whether currently
+     * a permit can be acquired and - if so - increases the internal counter. 
The return
+     * value indicates whether a permit could be acquired. This method must be 
called with
+     * the lock of this object held.
+     *
+     * @return a flag whether a permit could be acquired
+     */
+    private boolean acquirePermit() {
+        if (getLimit() <= NO_LIMIT || acquireCount < getLimit()) {
+            acquireCount++;
+            return true;
+        }
+        return false;
+    }
 }

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/809e2bed/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java 
b/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
index 067043d..3511021 100644
--- a/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
+++ b/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
@@ -385,6 +385,42 @@ public class TimedSemaphoreTest {
     }
 
     /**
+     * Tests the tryAcquire() method. It is checked whether the semaphore can 
be acquired
+     * by a bunch of threads the expected number of times and not more.
+     */
+    @Test
+    public void testTryAcquire() throws InterruptedException {
+        final TimedSemaphore semaphore = new TimedSemaphore(PERIOD, 
TimeUnit.SECONDS,
+                LIMIT);
+        TryAcquireThread[] threads = new TryAcquireThread[3 * LIMIT];
+        CountDownLatch latch = new CountDownLatch(1);
+        for (int i = 0; i < threads.length; i++) {
+            threads[i] = new TryAcquireThread(semaphore, latch);
+            threads[i].start();
+        }
+
+        latch.countDown();
+        int permits = 0;
+        for (TryAcquireThread t : threads) {
+            t.join();
+            if (t.acquired) {
+                permits++;
+            }
+        }
+        assertEquals("Wrong number of permits granted", LIMIT, permits);
+    }
+
+    /**
+     * Tries to call tryAcquire() after shutdown(). This should cause an 
exception.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testTryAcquireAfterShutdown() {
+        final TimedSemaphore semaphore = new TimedSemaphore(PERIOD, UNIT, 
LIMIT);
+        semaphore.shutdown();
+        semaphore.tryAcquire();
+    }
+
+    /**
      * A specialized implementation of {@code TimedSemaphore} that is easier to
      * test.
      */
@@ -495,4 +531,35 @@ public class TimedSemaphoreTest {
             }
         }
     }
+
+    /**
+     * A test thread class which invokes {@code tryAcquire()} on the test 
semaphore and
+     * records the return value.
+     */
+    private static class TryAcquireThread extends Thread {
+        /** The semaphore. */
+        private final TimedSemaphore semaphore;
+
+        /** A latch for communication with the main thread. */
+        private final CountDownLatch latch;
+
+        /** Flag whether a permit could be acquired. */
+        private boolean acquired;
+
+        public TryAcquireThread(TimedSemaphore s, CountDownLatch l) {
+            semaphore = s;
+            latch = l;
+        }
+
+        @Override
+        public void run() {
+            try {
+                if (latch.await(10, TimeUnit.SECONDS)) {
+                    acquired = semaphore.tryAcquire();
+                }
+            } catch (InterruptedException iex) {
+                // ignore
+            }
+        }
+    }
 }

Reply via email to