ExecutorService,按顺序执行任务但从池中获取线程

时间:2016-10-07 08:20:34

标签: java multithreading threadpool executorservice threadpoolexecutor

我正在尝试构建ExecutorService的实现,让我们将其称为SequentialPooledExecutor,具有以下属性。

  1. SequentialPooledExecutor的所有实例共享相同的线程池

  2. 按顺序执行对SequentialPooledExecutor的同一实例的调用。

  3. 换句话说,在开始处理队列中的下一个任务之前,实例会等待当前正在执行的任务的终止。

    我目前正在实施SequentialPooledExecutor我自己,但我想知道我是否正在重新发明轮子。我查看了ExecutorService的不同实现,例如Executors类提供的实现,但我没有找到符合我要求的实现。

    您是否知道我是否缺少现有的实施方案,还是应该继续自己实施界面?

    编辑:

    我认为我的要求不是很清楚,让我们看看我是否可以用其他词语来解释。

    假设我有一系列会话,比如1000个(我以前称之为执行程序实例的东西)。我可以向会话提交任务,我希望保证提交给同一会话的所有任务都按顺序执行。但是,属于不同会话的任务应该彼此没有依赖关系。

    我想定义执行这些任务的ExecutorService,但是使用有限数量的线程,让我们说200,但确保在同一个会话中的任务之前没有启动任务完了。

    我不知道是否存在已经存在的任何内容,或者我是否应该自己实现这样的ExecutorService

6 个答案:

答案 0 :(得分:3)

如果您有数千个必须按顺序处理的密钥,但是您没有数千个核心,则可以使用散列策略来分发此类工作

ExecutorService[] es = // many single threaded executors

public <T> Future<T> submit(String key, Callable<T> calls) {
    int h = Math.abs(key.hashCode() % es.length);
    return es[h].submit(calls);
}

一般情况下,只需要2 * N个线程就可以保持N个内核忙碌,如果你的任务是CPU限制的,那么只会增加开销。

答案 1 :(得分:2)

如果您想要按顺序执行任务,只需创建ExecutorService 只有一个帖子,这要归功于Executors.newSingleThreadExecutor()

如果您有不同类型的任务,并且您只想按顺序执行相同类型的任务,则可以使用相同单线程ExecutorService 相同类型任务,无需重新发明轮子。

所以,假设您有1 000种不同类型的任务,您可以使用200单线程ExecutorService,您需要自己实现的唯一事情就是您始终对于给定类型的任务,需要使用相同的单线程ExecutorService

答案 2 :(得分:1)

private Map<Integer, CompletableFuture<Void>> sessionTasks = new HashMap<>();
private ExecutorService pool = Executors.newFixedThreadPool(200);

public void submit(int sessionId, Runnable task) {  
    if (sessionTasks.containsKey(sessionId)) {
        sessionTasks.compute(sessionId, (i, c) -> c.thenRunAsync(task, pool));
    } else {
        sessionTasks.put(sessionId, CompletableFuture.runAsync(task, pool));
    }
}

如果会话没有任务,则会创建一个新任务并在提供的池中运行。如果会话在添加新任务时已经有任务,则后者将链接(使用thenRun)到前一个任务,确保顺序。

答案 3 :(得分:0)

@ Nicolas的回答可能是你最好的选择,因为它很简单,经过充分测试,效率很高。

如果它不能满足你的要求,我会这样做:

  1. 不要将“SequentialPooledExecutor”作为执行服务,使其成为单线程执行器服务“池”的外观
  2. 让你的“SequentialPooledExecutor”实现一个submit方法(使用Runnable / Callable和一个代表“队列名称”的String),返回一个Future,就像一个执行者服务
  3. 在调用此方法时,通过获取队列名称的哈希值并将其分派给相应的内部执行程序,将“SequentialPooledExecutor”调度到其内部单线程执行程序服务之一。
  4. 在步骤3中进行的散列部分允许您将每个“队列名称”的任务始终转到“SequentialPooledExecutor”内的相同(单线程)执行程序服务。

    另一种可能的途径是使用CompletionStageCompletableFutures。实际上,这些是可听的未来(具有完成处理程序)。有了这些,第一次进行“会话”时,您可以使用第一个任务创建CompletableFuture,并坚持下去。在每个新任务中,您将前一个未来与新任务相结合,调用thenAcceptAsync(或任何类似的)。你得到的是一系列执行任务的线性链接。

答案 4 :(得分:0)

如果要配置有界队列,请使用ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

对于您的使用案例,请使用ThreadPoolExecutor作为

ThreadPoolExecutor executor =    
ThreadPoolExecutor(1,1,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(1000));

以上代码大小的队列大小为ThreadPoolExecutor为1000.如果要使用自定义拒绝执行处理程序,则可以配置RejectedExeutionHandler

相关的SE问题:

How to properly use Java Executor?

答案 5 :(得分:0)

最近我遇到了同样的问题。 没有内置类,但队列足够接近。 我的简单实现看起来像这样(对于其他寻找同一问题示例的人来说,这可能很有帮助)

public class SerializedAsyncRunnerSimple implements Runnable {
private final ExecutorService pool;
protected final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); //thread safe queue
protected final AtomicBoolean active = new AtomicBoolean(false);


public SerializedAsyncRunnerSimple(ExecutorService threadPool) {this.pool = threadPool;}


public void addWork(Runnable r){        
    queue.add(r);
    startExecutionIfInactive();
}

private void startExecutionIfInactive() {
    if(active.compareAndSet(false, true)) {
        pool.execute(this);
    }
}

@Override
public synchronized void run() {
    while(!queue.isEmpty()){
        queue.poll().run();
    }
    active.set(false); //all future adds will not be executed on this thread anymore
    if(!queue.isEmpty()) { //if some items were added to the queue after the last queue.poll
        startExecutionIfInactive();// trigger an execution on next thread
    }
}
相关问题