此问题与最新版本的Java有关。
30个生产者线程将字符串推送到抽象队列。一个编写器线程从同一队列弹出,并将该字符串写入驻留在5400 rpm HDD RAID阵列上的文件。数据以大约111 MBps的速率推送,并以大约80MBps的速率弹出/写入。该程序存在5600秒,足以使大约176 GB的数据在队列中累积。另一方面,我限制为总共64GB的主内存。
我的问题是:我应该使用什么类型的队列?
这是我到目前为止所尝试的内容。
1)ArrayBlockingQueue
。这个有界队列的问题在于,无论数组的初始大小如何,一旦填满,我总是会遇到活动问题。实际上,在程序启动几秒后,top
仅报告一个活动线程。分析表明,平均而言,生产者线程花费大部分时间等待队列释放。这与我是否使用公平访问策略(构造函数中的第二个参数设置为true)无关。
2)ConcurrentLinkedQueue
。就活力而言,这个无限制的队列表现得更好。直到我耗尽内存,大约七百秒,所有三十个生产者线程都处于活动状态。然而,在我超过64GB限制之后,事情变得异常缓慢。我猜想这是因为分页问题,虽然我没有进行任何实验来证明这一点。
我预见到了两种出路。
1)购买SSD。希望I / O速率提高会有所帮助。
2)在写入文件之前压缩输出流。
还有其他选择吗?我是否遗漏了构造/使用上述队列的方式?是否有更聪明的方式来使用它们? Java Concurrency in Practice一书提出了一些饱和策略(第8.3.3节),在有限队列填充速度快于耗尽的情况下,但不幸的是,没有一个 - 中止,调用者运行,以及两个丢弃政策---适用于我的方案。
答案 0 :(得分:3)
寻找瓶颈。你生产的消耗量更多,有界的队列绝对有意义,因为你不想耗尽内存。
尽量让您的消费者更快。剖析并查看花费最多时间的位置。既然你在这里写了一些想法:
NIO
来解决问题吗? (也许FileChannel#transferTo()
)正如@Flavio已经说过的那样,对于生产者 - 消费者模式,我认为没有问题,应该是现在的方式。最后,最慢的一方控制速度。
答案 1 :(得分:2)
我在这里看不到问题。在生产者 - 消费者的情况下,系统将始终以较慢的一方的速度行事。如果生产者比消费者更快,那么当队列填满时,它将减慢到消费者的速度。
如果您的约束条件是您不能减慢生产者的速度,那么您必须找到加速消费者的方法。描述消费者(不要开始太花哨,一些System.nanoTime()
调用经常提供足够的信息),检查它花费大部分时间的地方,并从那里开始优化。如果您有CPU瓶颈,可以改进算法,添加更多线程等。如果您有磁盘瓶颈,请尝试少写(压缩是个好主意),获得更快的磁盘,写入两个磁盘而不是一个...
答案 2 :(得分:1)
根据java“Queue implementation”,还有其他适合您的类:
我不知道这些类的性能或内存使用情况,但您可以自己尝试。
我希望这会对你有所帮助。
答案 3 :(得分:1)
为什么你有30个生产者。这个号码是由问题域修复的,还是仅仅是您选择的号码?如果是后者,你应该减少生产者的数量,直到他们生产的总费率只比消费量大一点,并使用阻塞队列(正如其他人所建议的那样)。然后,您将使您的消费者忙碌,这是性能限制部分,同时最大限度地减少对其他资源(内存,线程)的使用。
答案 4 :(得分:1)
你只有两种方法:让供应商变慢或消费者更快。较慢的生产者可以通过多种方式完成,特别是使用有界队列。为了提高消费者的速度,请尝试https://www.google.ru/search?q=java+memory-mapped+file。看看https://github.com/peter-lawrey/Java-Chronicle。
另一种方法是从编写字符串写缓冲区的工作中释放写线程。让生产者线程发出就绪缓冲区,而不是字符串。使用有限数量的缓冲区,比如2 * threadnumber = 60。在开始时分配所有缓冲区,然后重复使用它们。将队列用于空缓冲区。生成线程从该队列中获取缓冲区,填充它并放入写入队列。编写线程从写入线程,写入磁盘并放入空缓冲区队列中获取缓冲区。
另一种方法是使用asynchronous I/O。生产者自己开始编写操作,没有特殊的写作线程。 Completion handler将使用过的缓冲区返回到空缓冲区队列中。