/* * Copyright 2009-2010 the original author or authors. * * Licensed 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.springframework.batch.admin.util; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.core.task.TaskRejectedException; import java.util.concurrent.FutureTask; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; /** * <p> * A {@link TaskExecutor} with a throttle limit which works by delegating to an * existing task executor and limiting the number of tasks submitted. * </p> * <p> * A throttle limit is provided to limit the number of pending requests over and * above the features provided by the other task executors. The submit method * blocks until there are results available to retrieve. This limit is different * (and orthogonal) to any queue size imposed by the delegate * {@link TaskExecutor}: such queues normally do not throttle, in the sense that * they always accept more work, until they fill up, at which point they reject. * The point of a throttle is to not reject any work, but to still limit the * number of concurrent tasks. * </p> * @author Dave Syer * */ public class ThrottledTaskExecutor implements TaskExecutor { private Semaphore semaphore; private volatile AtomicInteger count = new AtomicInteger(0); private TaskExecutor taskExecutor = new SyncTaskExecutor(); /** * Create a {@link ThrottledTaskExecutor} with infinite * (Integer.MAX_VALUE) throttle limit. A task can always be submitted. */ public ThrottledTaskExecutor() { this(null, Integer.MAX_VALUE); } /** * Create a {@link ThrottledTaskExecutor} with infinite * (Integer.MAX_VALUE) throttle limit. A task can always be submitted. * * @param taskExecutor the {@link TaskExecutor} to use */ public ThrottledTaskExecutor(TaskExecutor taskExecutor) { this(taskExecutor, Integer.MAX_VALUE); } /** * Create a {@link ThrottledTaskExecutor} with finite throttle * limit. The submit method will block when this limit is reached until one * of the tasks has finished. * * @param taskExecutor the {@link TaskExecutor} to use * @param throttleLimit the throttle limit */ public ThrottledTaskExecutor(TaskExecutor taskExecutor, int throttleLimit) { super(); if (taskExecutor != null) { this.taskExecutor = taskExecutor; } this.semaphore = new Semaphore(throttleLimit); } /** * Limits the number of concurrent executions on the enclosed task executor. * Do not call this after initialization (for configuration purposes only). * * @param throttleLimit the throttle limit to apply */ public void setThrottleLimit(int throttleLimit) { this.semaphore = new Semaphore(throttleLimit); } /** * Public setter for the {@link TaskExecutor} to be used to execute the * tasks submitted. The default is synchronous, executing tasks on the * calling thread. In this case the throttle limit is irrelevant as there * will always be at most one task pending. * * @param taskExecutor {@link org.springframework.core.task.TaskExecutor} */ public void setTaskExecutor(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } /** * Submit a task for execution by the delegate task executor, blocking if * the throttleLimit is exceeded. * * @see TaskExecutor#execute(Runnable) */ public void execute(Runnable task) { if (task == null) { throw new NullPointerException("Task is null in ThrottledTaskExecutor."); } doSubmit(task); } /** * Get an estimate of the number of pending requests. * * @return the estimate */ public int size() { return count.get(); } private Runnable doSubmit(final Runnable task) { try { semaphore.acquire(); count.incrementAndGet(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new TaskRejectedException("Task could not be submitted because of a thread interruption."); } try { taskExecutor.execute(new FutureTask<Object>(task, null) { @Override protected void done() { semaphore.release(); count.decrementAndGet(); } }); } catch (TaskRejectedException e) { semaphore.release(); count.decrementAndGet(); throw e; } return task; } }