Java中的线程安全队列和“master / worker”程序的模式/原则

时间:2009-07-22 11:38:33

标签: java algorithm multithreading data-structures queue

我有一个问题,我认为是经典的主/工模式,我正在寻求实施方面的建议。以下是我目前正在考虑的问题:

某种类型的全球“队列”,它是一个“保持工作”的中心位置。据推测,这个队列将由一种“主”对象管理。线程将被生成以找到要做的工作,当他们找到要做的工作时,他们会告诉主要事物(无论是什么)“将其添加到要完成的工作队列中”。

主人,也许是间隔时间,会产生其他实际完成工作的线程。一旦线程完成其工作,我希望它通知主服务器工作已完成。然后,主服务器可以从队列中删除此工作。

我以前在Java中完成了大量的线程编程,但它们都先于JDK 1.5,因此我不熟悉用于处理此案例的相应新API。我知道JDK7会有fork-join,这对我来说可能是一个解决方案,但是我无法在这个项目中使用早期访问产品。

我认为问题是:

1)如何让“线程完成工作”与主人沟通,告诉他们他们的工作已经完成,主人现在可以从队列中删除工作

2)如何有效地保证工作只安排一次。例如,假设这个队列有一百万个项目,它想告诉一个工人“去做这100件事”。什么是最有效的方法来保证当它为下一个工人安排工作时,它会得到“接下来的100件事”,而不是“我已经安排的100件事”?

3)为队列选择合适的数据结构。我在这里的想法是,“寻找工作要做的线程”可能会发现不止一次做同样的工作,并且他们会向主人发送一条消息说“这里的工作”,并且主人会意识到工作已经已经安排好,因此应该忽略该消息。我想确保选择正确的数据结构,以便这种计算尽可能便宜。

传统上,我会在数据库中以有限状态机的方式完成此任务,从开始到完成工作“任务”。但是,在这个问题中,我不想使用数据库,因为队列的数量和波动性很大。另外,我想尽量保持它的重量轻。如果可以避免,我不想使用任何应用服务器。

我所描述的这个问题很可能是一个众所周知的名称和一套公认的解决方案的常见问题,但我,我的低度非CS学位,不知道这是什么叫(即请温柔)。

感谢所有指针。

6 个答案:

答案 0 :(得分:7)

据我了解您的要求,您需要ExecutorService。 ExecutorService有

submit(Callable task)

返回值为Future的方法。未来是一种阻碍从工人到主人沟通的方式。您可以轻松地扩展此机制以异步方式工作。是的,ExecutorService还像ThreadPoolExecutor一样维护工作队列。因此,在大多数情况下,您不需要为调度而烦恼。 java.util.concurrent包已经有了线程安全队列的高效实现(ConcurrentLinked队列 - 非阻塞和LinkedBlockedQueue - 阻塞)。

答案 1 :(得分:4)

查看Java库中的java.util.concurrent

根据您的应用程序,它可能就像将一些阻塞队列和ThreadPoolExecutor拼凑在一起一样简单。

此外,Brian Goetz的书Java Concurrency in Practice可能会有所帮助。

答案 2 :(得分:4)

首先,为什么你想在工人开始做之后拿着这些物品?通常情况下,您将拥有一个工作队列,并且工作人员将项目从此队列中取出。这也将解决“如何防止工人获得相同的项目”问题。

问题:

  

1)如何让“线程做”   工作“与主人沟通   告诉他们他们的工作是   完成,主人现在可以   从队列中删除工作

主人可以使用listener/observer pattern

听取工人的意见
  2)如何有效地拥有主人   保证工作永远   预定一次。例如,让我们说   这个队列有一百万个项目   想告诉工人“去做这些   100件事。“什么是最有效的   保证当它的方式   它安排工作到下一个工人   获得“接下来的100件事”而不是   “我已经做了100件事   预定“?

见上文。我会让工人把物品拉出队列。

  

3)选择适当的数据   队列的结构。我的想法   这是“寻找工作的线索   做“可能会找到相同的   他们不止一次地工作   发消息给大师说   “这是工作”,主人会   意识到工作已经存在   预定,因此应该   忽略这条消息。我想确保   我选择了正确的数据结构   这样计算便宜   尽可能。

自Java 5以来有blocking queue的实现

答案 3 :(得分:1)

不要忘记Jini和Javaspaces。您所描述的内容听起来非常类似于基于空间的架构所擅长的经典制作人/消费者模式。

制作人将把作业写入空间。一个或多个消费者将取出工作(在交易下)并并行处理,然后将结果写回来。由于它处于交易之下,如果出现问题,该作业将再次为另一个消费者提供。

您可以通过添加更多消费者来轻松扩展此规模。当消费者是独立的虚拟机并且您可以跨网络扩展时,这种方法尤其有效。

答案 4 :(得分:0)

如果您对Spring的想法持开放态度,那么请查看他们的Spring Integration项目。它为您提供了开箱即用的所有队列/线程池样板,让您专注于业务逻辑。使用@annotations将配置保持在最低限度。

顺便说一下,Goetz非常好。

答案 5 :(得分:0)

这听起来不像是一个主工作者问题,而是一个专注于线程池的客户端。鉴于你有很多清理线程而不是很多处理单元,所以简单地做一个scavaging pass然后计算传递可能是值得的。通过将工作项存储在Set中,唯一性约束将删除重复项。第二遍可以将所有工作提交给ExecutorService以并行执行该过程。

主工作者模型通常假定数据提供者具有所有工作并将其提供给主人来管理。主控制工作执行并处理分布式计算,超时,失败,重试等。分叉连接抽象是一种递归而非迭代的数据提供者。 map-reduce抽象是一个多步骤的master-worker,在某些场景中很有用。

主工作者的一个很好的例子是平凡的并行问题,例如查找素数。另一个是数据加载,其中每个条目都是独立的(验证,转换,阶段)。处理已知工作集,处理故障等的需要是使主工作者模型与线程池不同的原因。这就是为什么一个主人必须控制并推出工作单元,而一个线程池允许工人从共享队列中拉出工作。