寻找基于ThreadPoolExecutor的解决方案,以确保顺序执行任务

时间:2015-02-16 16:01:31

标签: java multithreading scheduling threadpoolexecutor

我有一个m个线程的线程池。让我们说m是10并修复。然后有n个队列,有可能n变大(如100&#39,000或更多)。每个队列都保存由m个线程执行的任务。现在,非常重要的是,每个队列必须按任务顺序处理。这是确保任务按照添加到队列的顺序执行的要求。否则数据可能变得不一致(例如,与JMS队列相同)。

现在的问题是如何确保这些n个队列中的任务由可用的m个线程处理,其方式是不能执行添加到同一队列的任务"同时#34 ;由不同的线程。

我试图自己解决这个问题并发现它非常苛刻。 Java ThreadPoolExecutor很不错,但你必须添加一些不易开发的功能。所以问题是,是否有人知道某些已经解决了这个问题的Java框架或系统?

更新

感谢Adrian和Tanmay的建议。队列的数量可能非常大(例如100&#39,000或更多)。因此,每个队列中的一个线程是不可能的,尽管它简单易行。我将研究fork join框架。看起来像是一条有趣的道路。

我目前的第一个迭代解决方案是拥有一个全局队列,所有任务都添加到该队列中(使用JDK8 TransferQueue,它具有非常小的锁定开销)。任务被包装到队列存根中,其中包含队列的锁定及其大小。队列本身不存在物理存在,只存在其存根。

空闲线程首先需要获取令牌才能访问全局队列(令牌将是阻塞队列中的单个元素,例如JDK8 TransferQueue)。然后它对全局队列进行阻塞。获取任务后,它会检查任务队列存根的队列锁定是否已关闭。实际上,我认为使用AtomicBoolean就足够了,并且比锁或同步块创建更少的锁争用。

获取队列锁定后,令牌将返回到全局队列并执行任务。如果未获取,则将任务添加到第二级队列,并完成从全局队列的另一个阻塞。线程需要检查第二级队列是否为空,并从中执行任务以执行。

此解决方案似乎有效。但是,每个线程在被允许访问全局队列之前需要获取的令牌,而第二级队列看起来像瓶颈。我相信它会产生高锁争用。所以,我对此不太满意。也许我从这个解决方案开始并详细阐述它。

更新2

好的,现在这里是"最好的"到目前为止我已经提出了解决方案。定义了以下队列:

Ready Queue(RQ):包含线程池中任何线程可立即执行的所有任务

Entry Queue(EQ):包含用户想要执行的所有任务以及内部管理任务。 EQ是优先级队列。管理任务具有最高优先级。

频道队列(CQ):对于每个频道,都有一个内部频道队列,用于保存任务的顺序,例如:确保按照将它们添加到EQ

的顺序依次执行任务

调度程序:从EQ获取任务的专用线程。如果任务是用户任务,则将其添加到任务添加到的频道的CQ中。如果CQ的头部等于刚刚插入的用户任务,则它也被添加到EQ(但保留在CQ中),以便一旦线程池的下一个线程变得可用就执行它。

如果用户任务已完成执行,则内部任务TaskFinished将添加到RQ。当由调度器执行时,头部取自关联的CQ。如果在获取之后CQ不为空,则从CQ轮询(但不采用)下一个任务并将其添加到RQ。 TaskFinished任务的优先级高于用户任务。

在我看来,这种方法没有逻辑错误。请注意,EQ和RQ需要同步。我更喜欢使用JDK8中的TransferQueue,它非常快,并且检查它是否为空,轮询头项目也非常快。 CQ不需要同步,因为它们总是仅由调度程序访问。

到目前为止,我对这个解决方案非常满意。让我想到的是调度程序是否会成为瓶颈。如果EQ中的任务比它可以处理的任务多得多,那么EQ可能会增加一些积压。任何有关这方面的意见将不胜感激: - )

2 个答案:

答案 0 :(得分:0)

如果您使用的是Java 7或Java 8,则可以使用 Fork Join Framework

您可以使用每个队列中弹出的第一个元素创建RecursiveTask 请记住提供对相应RecursiveTask的队列的引用。

立即调用所有内容。 (在循环或流中)。

现在在compute方法结束时(在完成任务处理之后),通过从相应队列中弹出另一个元素并在其上调用RecursiveTask来创建另一个invoke


备注:

  • 每个任务都负责从队列中提取新元素,因此队列中的所有任务都将按顺序执行。
  • 应该为队列中的每个元素单独创建和调用新的RecursiveTask。这可以确保某些队列不会占用线程并避免饥饿。
  • 使用ExecutorService也是一个可行的选择,但IMO ForkJoin的API如果您的用例更友好

希望这有帮助。

答案 1 :(得分:0)

一个简单的解决方案是在将元素添加到空队列时创建任务。此任务仅负责该队列,并在队列处理完毕后结束。确保Queue实现是线程安全的,并且在删除最后一个元素后任务停止。

编辑:这些任务应该添加到带有内部队列的ThreadPoolExecutor中,例如由ExecutorService.newFixedThreadPool创建的队列,它将与有限数量的线程并行处理任务。


或者,只需将队列划分为固定数量的线程:

public class QueueWorker implements Runnable {
    // should be unique and < NUM_THREADS:
    int threadId;

    QueueWorker(int threadId) {
        this.threadId = threadId;
    }

    @Override
    public void run() {
        int currentQueueIndex = threadId;
        while (true) {
            Queue currentQueue = queues.get(currentQueue);
            // execute tasks until empty
            currentQueueIndex += NUM_THREADS;
            if (currentQueueIndex > queues.size()) {
                currentQueueIndex = threadId;
            }
        }
    }
}