/** * Tests the methods for statistics. * * @throws java.lang.InterruptedException so we don't have to catch it */ @Test public void testGetAverageCallsPerPeriod() throws InterruptedException { 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, UNIT, LIMIT); semaphore.acquire(); semaphore.endOfPeriod(); assertEquals("Wrong average (1)", 1.0, semaphore .getAverageCallsPerPeriod(), .005); semaphore.acquire(); semaphore.acquire(); semaphore.endOfPeriod(); assertEquals("Wrong average (2)", 1.5, semaphore .getAverageCallsPerPeriod(), .005); EasyMock.verify(service, future); }
/** * Returns the number of calls to the {@link #acquire()} method that can * still be performed in the current period without blocking. This method * can give an indication whether it is safe to call the {@link #acquire()} * method without risking to be suspended. However, there is no guarantee * that a subsequent call to {@link #acquire()} actually is not-blocking * because in the mean time other threads may have invoked the semaphore. * * @return the current number of available {@link #acquire()} calls in the * current period */ public synchronized int getAvailablePermits() { return getLimit() - getAcquireCount(); }
/** * Starts the timer. This method is called when {@link #acquire()} is called * for the first time. It schedules a task to be executed at fixed rate to * monitor the time period specified. * * @return a future object representing the task scheduled */ protected ScheduledFuture<?> startTimer() { return getExecutorService().scheduleAtFixedRate(new Runnable() { @Override public void run() { endOfPeriod(); } }, getPeriod(), getPeriod(), getUnit()); }
/** * 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(); }
/** * 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(); } }
/** * Tests creating a new instance. */ @Test public void testInit() { final ScheduledExecutorService service = EasyMock .createMock(ScheduledExecutorService.class); EasyMock.replay(service); final TimedSemaphore semaphore = new TimedSemaphore(service, PERIOD, UNIT, LIMIT); EasyMock.verify(service); assertEquals("Wrong service", service, semaphore.getExecutorService()); assertEquals("Wrong period", PERIOD, semaphore.getPeriod()); assertEquals("Wrong unit", UNIT, semaphore.getUnit()); assertEquals("Statistic available", 0, semaphore .getLastAcquiresPerPeriod()); assertEquals("Average available", 0.0, semaphore .getAverageCallsPerPeriod(), .05); assertFalse("Already shutdown", semaphore.isShutdown()); assertEquals("Wrong limit", LIMIT, semaphore.getLimit()); }
final int count = 10; final CountDownLatch latch = new CountDownLatch(count - 1); final TimedSemaphore semaphore = new TimedSemaphore(service, PERIOD, UNIT, 1); final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count, count - 1); semaphore.setLimit(count - 1); .getAcquireCount()); semaphore.endOfPeriod(); t.join(); assertEquals("Wrong semaphore count (2)", 1, semaphore .getAcquireCount()); assertEquals("Wrong acquire() count", count - 1, semaphore .getLastAcquiresPerPeriod()); EasyMock.verify(service, future);
/** * Tests the shutdown() method if the executor belongs to the semaphore. In * this case it has to be shut down. */ @Test public void testShutdownOwnExecutor() { final TimedSemaphore semaphore = new TimedSemaphore(PERIOD, UNIT, LIMIT); semaphore.shutdown(); assertTrue("Not shutdown", semaphore.isShutdown()); assertTrue("Executor not shutdown", semaphore.getExecutorService() .isShutdown()); }
/** * Tests whether the available non-blocking calls can be queried. * * @throws java.lang.InterruptedException so we don't have to catch it */ @Test public void testGetAvailablePermits() throws InterruptedException { 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, UNIT, LIMIT); for (int i = 0; i < LIMIT; i++) { assertEquals("Wrong available count at " + i, LIMIT - i, semaphore .getAvailablePermits()); semaphore.acquire(); } semaphore.endOfPeriod(); assertEquals("Wrong available count in new period", LIMIT, semaphore .getAvailablePermits()); EasyMock.verify(service, future); }
@Override public void run() { endOfPeriod(); } }, getPeriod(), getPeriod(), getUnit());
/** * Tests the shutdown() method for a shared executor service before a task * was started. This should do pretty much nothing. */ @Test public void testShutdownSharedExecutorNoTask() { final ScheduledExecutorService service = EasyMock .createMock(ScheduledExecutorService.class); EasyMock.replay(service); final TimedSemaphore semaphore = new TimedSemaphore(service, PERIOD, UNIT, LIMIT); semaphore.shutdown(); assertTrue("Not shutdown", semaphore.isShutdown()); EasyMock.verify(service); }
/** * Tests whether a default executor service is created if no service is * provided. */ @Test public void testInitDefaultService() { final TimedSemaphore semaphore = new TimedSemaphore(PERIOD, UNIT, LIMIT); final ScheduledThreadPoolExecutor exec = (ScheduledThreadPoolExecutor) semaphore .getExecutorService(); assertFalse("Wrong periodic task policy", exec .getContinueExistingPeriodicTasksAfterShutdownPolicy()); assertFalse("Wrong delayed task policy", exec .getExecuteExistingDelayedTasksAfterShutdownPolicy()); assertFalse("Already shutdown", exec.isShutdown()); semaphore.shutdown(); }
/** * 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; } }
/** * Initializes a shutdown. After that the object cannot be used any more. * This method can be invoked an arbitrary number of times. All invocations * after the first one do not have any effect. */ public synchronized void shutdown() { if (!shutdown) { if (ownExecutor) { // if the executor was created by this instance, it has // to be shutdown getExecutorService().shutdownNow(); } if (task != null) { task.cancel(false); } shutdown = true; } }
/** * Creates a new instance of {@link TimedSemaphore} and initializes it with * an executor service, the given time period, and the limit. The executor * service will be used for creating a periodic task for monitoring the time * period. It can be <b>null</b>, then a default service will be created. * * @param service the executor service * @param timePeriod the time period * @param timeUnit the unit for the period * @param limit the limit for the semaphore * @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); }
/** * Either returns the mock future or calls the super method. */ @Override protected ScheduledFuture<?> startTimer() { return schedFuture != null ? schedFuture : super.startTimer(); } }
/** * 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 * task which monitors the time period set for this {@code TimedSemaphore}. * From now on the semaphore is active. * * @throws InterruptedException if the thread gets interrupted * @throws IllegalStateException if this semaphore is already shut down */ public synchronized void acquire() throws InterruptedException { prepareAcquire(); boolean canPass; do { canPass = acquirePermit(); if (!canPass) { wait(); } } while (!canPass); }
/** * 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(); } }
/** * Counts the number of invocations. */ @Override protected synchronized void endOfPeriod() { super.endOfPeriod(); periodEnds++; }
/** * 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; } }