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 df3e2715f Add TimedSemaphore.builder() and Builder
df3e2715f is described below
commit df3e2715f41263d8998b7350301d106967748c0e
Author: Gary D. Gregory <[email protected]>
AuthorDate: Tue Oct 14 16:15:35 2025 -0400
Add TimedSemaphore.builder() and Builder
---
src/changes/changes.xml | 1 +
.../commons/lang3/concurrent/TimedSemaphore.java | 114 ++++++++++++++++++---
.../lang3/concurrent/TimedSemaphoreTest.java | 84 ++++++---------
3 files changed, 130 insertions(+), 69 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 95a239b84..fdd80bc2d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -59,6 +59,7 @@ The <action> type attribute can be add,update,fix,remove.
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add JavaVersion.JAVA_25.</action>
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add SystemUtils.IS_JAVA_25.</action>
<action type="add" dev="ggregory" due-to="jack5505, Gary
Gregory">Add MutablePair.ofNonNull(Map.Entry).</action>
+ <action type="add" dev="ggregory" due-to="Gary
Gregory">Add TimedSemaphore.builder() and Builder.</action>
<!-- UPDATE -->
<action type="update" dev="ggregory" due-to="Gary
Gregory">Bump org.apache.commons:commons-parent from 88 to 89.</action>
</release>
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 10ffef62c..3960c64ba 100644
--- a/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
+++ b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
@@ -21,6 +21,7 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import org.apache.commons.lang3.Validate;
@@ -111,6 +112,75 @@
*/
public class TimedSemaphore {
+ /**
+ * Builds new {@link TimedSemaphore}.
+ *
+ * @since 3.20.0
+ */
+ public static class Builder implements Supplier<TimedSemaphore> {
+
+ private ScheduledExecutorService service;
+ private long period;
+ private TimeUnit timeUnit;
+ private int limit;
+
+ /**
+ * Constructs a new Builder.
+ */
+ public Builder() {
+ // empty
+ }
+
+ @Override
+ public TimedSemaphore get() {
+ return new TimedSemaphore(this);
+ }
+
+ /**
+ * Sets the limit.
+ *
+ * @param limit The limit.
+ * @return {@code this} instance.
+ */
+ public Builder setLimit(final int limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ /**
+ * Sets the time period.
+ *
+ * @param period The time period.
+ * @return {@code this} instance.
+ */
+ public Builder setPeriod(final long period) {
+ this.period = period;
+ return this;
+ }
+
+ /**
+ * Sets the executor service.
+ *
+ * @param service The executor service.
+ * @return {@code this} instance.
+ */
+ public Builder setService(final ScheduledExecutorService service) {
+ this.service = service;
+ return this;
+ }
+
+ /**
+ * Sets the time unit for the period.
+ *
+ * @param timeUnit The time unit for the period.
+ * @return {@code this} instance.
+ */
+ public Builder setTimeUnit(final TimeUnit timeUnit) {
+ this.timeUnit = timeUnit;
+ return this;
+ }
+ }
+
/**
* Constant for a value representing no limit. If the limit is set to a
value less or equal this constant, the {@link TimedSemaphore} will be
effectively
* switched off.
@@ -120,10 +190,20 @@ public class TimedSemaphore {
/** Constant for the thread pool size for the executor. */
private static final int THREAD_POOL_SIZE = 1;
+ /**
+ * Constructs a new Builder.
+ *
+ * @return a new Builder.
+ * @since 3.20.0
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
/** The executor service for managing the timer thread. */
private final ScheduledExecutorService executorService;
- /** Stores the period for this timed semaphore. */
+ /** The period for this timed semaphore. */
private final long period;
/** The time unit for the period. */
@@ -155,6 +235,23 @@ public class TimedSemaphore {
/** A flag whether shutdown() was called. */
private boolean shutdown; // @GuardedBy("this")
+ private TimedSemaphore(final Builder builder) {
+ Validate.inclusiveBetween(1, Long.MAX_VALUE, builder.period, "Time
period must be greater than 0.");
+ period = builder.period;
+ unit = builder.timeUnit;
+ if (builder.service != null) {
+ executorService = builder.service;
+ ownExecutor = false;
+ } else {
+ final ScheduledThreadPoolExecutor stpe = new
ScheduledThreadPoolExecutor(THREAD_POOL_SIZE);
+ stpe.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+ stpe.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+ executorService = stpe;
+ ownExecutor = true;
+ }
+ setLimit(builder.limit);
+ }
+
/**
* Constructs a new instance of {@link TimedSemaphore} and initializes it
with the given time period and the limit.
*
@@ -178,20 +275,7 @@ public TimedSemaphore(final long timePeriod, final
TimeUnit timeUnit, final int
* @throws IllegalArgumentException if the period is less or equals 0.
*/
public TimedSemaphore(final ScheduledExecutorService service, final long
timePeriod, final TimeUnit timeUnit, final int limit) {
- Validate.inclusiveBetween(1, Long.MAX_VALUE, timePeriod, "Time period
must be greater than 0!");
- period = timePeriod;
- unit = timeUnit;
- if (service != null) {
- executorService = service;
- ownExecutor = false;
- } else {
- final ScheduledThreadPoolExecutor s = new
ScheduledThreadPoolExecutor(THREAD_POOL_SIZE);
- s.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
- s.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
- executorService = s;
- ownExecutor = true;
- }
- setLimit(limit);
+
this(builder().setService(service).setPeriod(timePeriod).setTimeUnit(timeUnit).setLimit(limit));
}
/**
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 f6b4e2062..6cd284426 100644
--- a/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
+++ b/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
@@ -36,15 +36,17 @@
import org.junit.jupiter.api.Test;
/**
- * Test class for TimedSemaphore.
+ * Tests {@link TimedSemaphore}.
*/
class TimedSemaphoreTest extends AbstractLangTest {
+
/**
* A test thread class that will be used by tests for triggering the
* semaphore. The thread calls the semaphore a configurable number of
times.
* When this is done, it cannotify the main thread.
*/
private static final class SemaphoreThread extends Thread {
+
/** The semaphore. */
private final TimedSemaphore semaphore;
@@ -90,6 +92,7 @@ public void run() {
* test.
*/
private static final class TimedSemaphoreTestImpl extends TimedSemaphore {
+
/** A mock scheduled future. */
ScheduledFuture<?> schedFuture;
@@ -99,13 +102,11 @@ private static final class TimedSemaphoreTestImpl extends
TimedSemaphore {
/** Counter for the endOfPeriod() invocations. */
private int periodEnds;
- TimedSemaphoreTestImpl(final long timePeriod, final TimeUnit timeUnit,
- final int limit) {
+ TimedSemaphoreTestImpl(final long timePeriod, final TimeUnit timeUnit,
final int limit) {
super(timePeriod, timeUnit, limit);
}
- TimedSemaphoreTestImpl(final ScheduledExecutorService service,
- final long timePeriod, final TimeUnit timeUnit, final int
limit) {
+ TimedSemaphoreTestImpl(final ScheduledExecutorService service, final
long timePeriod, final TimeUnit timeUnit, final int limit) {
super(service, timePeriod, timeUnit, limit);
}
@@ -156,6 +157,7 @@ protected ScheduledFuture<?> startTimer() {
* records the return value.
*/
private static final class TryAcquireThread extends Thread {
+
/** The semaphore. */
private final TimedSemaphore semaphore;
@@ -201,8 +203,7 @@ public void run() {
*/
private void prepareStartTimer(final ScheduledExecutorService service,
final ScheduledFuture<?> future) {
- service.scheduleAtFixedRate((Runnable) EasyMock.anyObject(), EasyMock
- .eq(PERIOD_MILLIS), EasyMock.eq(PERIOD_MILLIS),
EasyMock.eq(UNIT));
+ service.scheduleAtFixedRate((Runnable) EasyMock.anyObject(),
EasyMock.eq(PERIOD_MILLIS), EasyMock.eq(PERIOD_MILLIS), EasyMock.eq(UNIT));
EasyMock.expectLastCall().andReturn(future);
}
@@ -213,24 +214,20 @@ private void prepareStartTimer(final
ScheduledExecutorService service,
*/
@Test
void testAcquireLimit() throws InterruptedException {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
final ScheduledFuture<?> future =
EasyMock.createMock(ScheduledFuture.class);
prepareStartTimer(service, future);
EasyMock.replay(service, future);
final int count = 10;
final CountDownLatch latch = new CountDownLatch(count - 1);
final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT, 1);
- final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count,
- count - 1);
+ final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count,
count - 1);
semaphore.setLimit(count - 1);
-
// start a thread that calls the semaphore count times
t.start();
latch.await();
// now the semaphore's limit should be reached and the thread blocked
assertEquals(count - 1, semaphore.getAcquireCount(), "Wrong semaphore
count");
-
// this wakes up the thread, it should call the semaphore once more
semaphore.endOfPeriod();
t.join();
@@ -250,8 +247,7 @@ void testAcquireLimit() throws InterruptedException {
@Test
void testAcquireMultiplePeriods() throws InterruptedException {
final int count = 1000;
- final TimedSemaphoreTestImpl semaphore = new TimedSemaphoreTestImpl(
- PERIOD_MILLIS / 10, TimeUnit.MILLISECONDS, 1);
+ final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(PERIOD_MILLIS / 10, TimeUnit.MILLISECONDS, 1);
semaphore.setLimit(count / 4);
final CountDownLatch latch = new CountDownLatch(count);
final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count,
count);
@@ -271,13 +267,11 @@ void testAcquireMultiplePeriods() throws
InterruptedException {
*/
@Test
void testAcquireMultipleThreads() throws InterruptedException {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
final ScheduledFuture<?> future =
EasyMock.createMock(ScheduledFuture.class);
prepareStartTimer(service, future);
EasyMock.replay(service, future);
- final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(service,
- PERIOD_MILLIS, UNIT, 1);
+ final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(service, PERIOD_MILLIS, UNIT, 1);
semaphore.latch = new CountDownLatch(1);
final int count = 10;
final SemaphoreThread[] threads = new SemaphoreThread[count];
@@ -307,13 +301,11 @@ void testAcquireMultipleThreads() throws
InterruptedException {
*/
@Test
void testAcquireNoLimit() throws InterruptedException {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
final ScheduledFuture<?> future =
EasyMock.createMock(ScheduledFuture.class);
prepareStartTimer(service, future);
EasyMock.replay(service, future);
- final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(service,
- PERIOD_MILLIS, UNIT, TimedSemaphore.NO_LIMIT);
+ final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(service, PERIOD_MILLIS, UNIT, TimedSemaphore.NO_LIMIT);
final int count = 1000;
final CountDownLatch latch = new CountDownLatch(count);
final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count,
count);
@@ -329,13 +321,11 @@ void testAcquireNoLimit() throws InterruptedException {
*/
@Test
void testGetAvailablePermits() throws InterruptedException {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
final ScheduledFuture<?> future =
EasyMock.createMock(ScheduledFuture.class);
prepareStartTimer(service, future);
EasyMock.replay(service, future);
- final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT,
- LIMIT);
+ final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT, LIMIT);
for (int i = 0; i < LIMIT; i++) {
assertEquals(LIMIT - i, semaphore.getAvailablePermits(), "Wrong
available count at " + i);
semaphore.acquire();
@@ -352,13 +342,11 @@ void testGetAvailablePermits() throws
InterruptedException {
*/
@Test
void testGetAverageCallsPerPeriod() throws InterruptedException {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
final ScheduledFuture<?> future =
EasyMock.createMock(ScheduledFuture.class);
prepareStartTimer(service, future);
EasyMock.replay(service, future);
- final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT,
- LIMIT);
+ final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT, LIMIT);
semaphore.acquire();
semaphore.endOfPeriod();
assertEquals(1.0, semaphore.getAverageCallsPerPeriod(), .005, "Wrong
average (1)");
@@ -374,11 +362,9 @@ void testGetAverageCallsPerPeriod() throws
InterruptedException {
*/
@Test
void testInit() {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
EasyMock.replay(service);
- final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT,
- LIMIT);
+ final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT, LIMIT);
EasyMock.verify(service);
assertEquals(service, semaphore.getExecutorService(), "Wrong service");
assertEquals(PERIOD_MILLIS, semaphore.getPeriod(), "Wrong period");
@@ -396,8 +382,7 @@ void testInit() {
@Test
void testInitDefaultService() {
final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS,
UNIT, LIMIT);
- final ScheduledThreadPoolExecutor exec = (ScheduledThreadPoolExecutor)
semaphore
- .getExecutorService();
+ final ScheduledThreadPoolExecutor exec = (ScheduledThreadPoolExecutor)
semaphore.getExecutorService();
assertFalse(exec.getContinueExistingPeriodicTasksAfterShutdownPolicy(), "Wrong
periodic task policy");
assertFalse(exec.getExecuteExistingDelayedTasksAfterShutdownPolicy(),
"Wrong delayed task policy");
assertFalse(exec.isShutdown(), "Already shutdown");
@@ -430,14 +415,12 @@ void testPassAfterShutdown() {
*/
@Test
void testShutdownMultipleTimes() throws InterruptedException {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
final ScheduledFuture<?> future =
EasyMock.createMock(ScheduledFuture.class);
prepareStartTimer(service, future);
EasyMock.expect(Boolean.valueOf(future.cancel(false))).andReturn(Boolean.TRUE);
EasyMock.replay(service, future);
- final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(service,
- PERIOD_MILLIS, UNIT, LIMIT);
+ final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(service, PERIOD_MILLIS, UNIT, LIMIT);
semaphore.acquire();
for (int i = 0; i < 10; i++) {
semaphore.shutdown();
@@ -463,11 +446,9 @@ void testShutdownOwnExecutor() {
*/
@Test
void testShutdownSharedExecutorNoTask() {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
EasyMock.replay(service);
- final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT,
- LIMIT);
+ final TimedSemaphore semaphore = new TimedSemaphore(service,
PERIOD_MILLIS, UNIT, LIMIT);
semaphore.shutdown();
assertTrue(semaphore.isShutdown(), "Not shutdown");
EasyMock.verify(service);
@@ -481,14 +462,12 @@ void testShutdownSharedExecutorNoTask() {
*/
@Test
void testShutdownSharedExecutorTask() throws InterruptedException {
- final ScheduledExecutorService service = EasyMock
- .createMock(ScheduledExecutorService.class);
+ final ScheduledExecutorService service =
EasyMock.createMock(ScheduledExecutorService.class);
final ScheduledFuture<?> future =
EasyMock.createMock(ScheduledFuture.class);
prepareStartTimer(service, future);
EasyMock.expect(Boolean.valueOf(future.cancel(false))).andReturn(Boolean.TRUE);
EasyMock.replay(service, future);
- final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(service,
- PERIOD_MILLIS, UNIT, LIMIT);
+ final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(service, PERIOD_MILLIS, UNIT, LIMIT);
semaphore.acquire();
semaphore.shutdown();
assertTrue(semaphore.isShutdown(), "Not shutdown");
@@ -502,8 +481,7 @@ void testShutdownSharedExecutorTask() throws
InterruptedException {
*/
@Test
void testStartTimer() throws InterruptedException {
- final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(PERIOD_MILLIS,
- UNIT, LIMIT);
+ final TimedSemaphoreTestImpl semaphore = new
TimedSemaphoreTestImpl(PERIOD_MILLIS, UNIT, LIMIT);
final ScheduledFuture<?> future = semaphore.startTimer();
assertNotNull(future, "No future returned");
ThreadUtils.sleepQuietly(DURATION);
@@ -522,15 +500,13 @@ void testStartTimer() throws InterruptedException {
*/
@Test
void testTryAcquire() throws InterruptedException {
- final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS,
TimeUnit.SECONDS,
- LIMIT);
+ final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS,
TimeUnit.SECONDS, LIMIT);
final TryAcquireThread[] threads = new TryAcquireThread[3 * LIMIT];
final 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 (final TryAcquireThread t : threads) {