我有一个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可能会增加一些积压。任何有关这方面的意见将不胜感激: - )
答案 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;
}
}
}
}