问题
I'm using java.util.concurrent.ExecutorService with fixed thread pool to execute list of tasks. My list of tasks will typically be around 80 - 150 and I've limited the number of threads running at any time to 10 as shown below:
ExecutorService threadPoolService = Executors.newFixedThreadPool(10);
for ( Runnable task : myTasks )
{
threadPoolService.submit(task);
}
My use case demands that even the completed task should be re-submitted again to the ExecutorService but it should be executed/taken again only when all the already submitted tasks are serviced/completed. That is basically, the tasks submitted should be executed on a rotation-basis. Hence, there will not be either threadPoolService.shutdown() or threadPoolService.shutdownNow() call in this case.
My question is, how do I implement ExecutorService servicing rotation-basis tasks?
回答1:
ThreadPoolExecutor provides an extension point for afterExecution where you can put the job back at the end of the queue.
public class TaskRepeatingThreadPoolExecutor extends ThreadPoolExecutor {
public TaskRepeatingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
this.submit(r);
}
}
You'll have to do a little more work of course to instantiate it yourself without the help of ExecutorService's handy factory method, but the constructors are simple enough to grok.
回答2:
The answer is more related to the implementation of the work queue used for the instance of ExecutorService. So, I'd suggest:
First choose an implementation of java.util.concurrent.BlockingQueue (an example) that provides a circular queue functionality. NOTE, the reason
BlockingQueuehas been chosen is that to wait until the next task is provided to queue; so, in case of circular + blocking queue, you should be careful how to provide the same behavior and functionality.Instead of using
Executors.new...to create a newThreadPoolExecutoruse a direct constructor such as
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
This way, unless you command the executor to shutdown, it will try to fetch the next task from the queue for execution from its work queue which is a circular container for tasks.
回答3:
I suggest the following solution which completely uses functionality existing in the standard library concurrency utils. It uses a CyclicBarrier with a task decorator class and a barrier action which re-submits all tasks:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Rotation {
private static final class RotationDecorator implements Runnable {
private final Runnable task;
private final CyclicBarrier barrier;
RotationDecorator( Runnable task, CyclicBarrier barrier ) {
this.task = task;
this.barrier = barrier;
}
@Override
public void run() {
this.task.run();
try {
this.barrier.await();
} catch(InterruptedException e) {
; // Consider better exception handling
} catch(BrokenBarrierException e) {
; // Consider better exception handling
}
}
}
public void startRotation( List<Runnable> tasks ) {
final ExecutorService threadPoolService = Executors.newFixedThreadPool( 10 );
final List<Runnable> rotatingTasks = new ArrayList<Runnable>( tasks.size() );
final CyclicBarrier barrier = new CyclicBarrier( tasks.size(), new Runnable() {
@Override
public void run() {
Rotation.this.enqueueTasks( threadPoolService, rotatingTasks );
}
} );
for(Runnable task : tasks) {
rotatingTasks.add( new RotationDecorator( task, barrier ) );
}
this.enqueueTasks( threadPoolService, rotatingTasks );
}
private void enqueueTasks( ExecutorService service, List<Runnable> tasks ) {
for(Runnable task : tasks) {
service.submit( task );
}
}
}
回答4:
You could simply check that all the tasks have been executed and resubmit them once it is the case, like this for example:
List<Future> futures = new ArrayList<>();
for (Runnable task : myTasks) {
futures.add(threadPoolService.submit(task));
}
//wait until completion of all tasks
for (Future f : futures) {
f.get();
}
//restart
......
EDIT
It seems you want to resubmit a task as soon as it gets completed. You could use an ExecutorCompletionService which enables you to retrieve tasks as and when they get executed, - see below a simple example with 2 tasks that get resubmitted a few times as soon as they are completed. Sample output:
Task 1 submitted pool-1-thread-1
Task 2 submitted pool-1-thread-2
Task 1 completed pool-1-thread-1
Task 1 submitted pool-1-thread-3
Task 2 completed pool-1-thread-2
Task 1 completed pool-1-thread-3
Task 2 submitted pool-1-thread-4
Task 1 submitted pool-1-thread-5
Task 1 completed pool-1-thread-5
Task 2 completed pool-1-thread-4
public class Test1 {
public final ConcurrentMap<String, String> concurrentMap = new ConcurrentHashMap<>();
public final AtomicInteger retries = new AtomicInteger();
public final Object lock = new Object();
public static void main(String[] args) throws InterruptedException, ExecutionException {
int count = 0;
List<Runnable> myTasks = new ArrayList<>();
myTasks.add(getRunnable(1));
myTasks.add(getRunnable(2));
ExecutorService threadPoolService = Executors.newFixedThreadPool(10);
CompletionService<Runnable> ecs = new ExecutorCompletionService<Runnable>(threadPoolService);
for (Runnable task : myTasks) {
ecs.submit(task, task);
}
//wait until completion of all tasks
while(count++ < 3) {
Runnable task = ecs.take().get();
ecs.submit(task, task);
}
threadPoolService.shutdown();
}
private static Runnable getRunnable(final int i) {
return new Runnable() {
@Override
public void run() {
System.out.println("Task " + i + " submitted " + Thread.currentThread().getName() + " ");
try {
Thread.sleep(500 * i);
} catch (InterruptedException ex) {
System.out.println("Interrupted");
}
System.out.println("Task " + i + " completed " + Thread.currentThread().getName() + " ");
}
};
}
}
来源:https://stackoverflow.com/questions/10346187/how-to-implement-an-executorservice-to-execute-tasks-on-a-rotation-basis