我正在研究一个标准的Java系统,它对我的生产者有严格的时序要求(1 / 100s ms事项)。
我有一个生产者将东西放在一个阻塞队列中,而一个消费者随后将这些东西收集起来并将其转储到一个文件中。消费者在数据不可用时阻止。
显然,阻塞队列是适当的接口,但是如果我想最小化生产者的成本,我应该选择哪种实际实现?当我把东西放进队列时,我想尽可能少地玩锁定和分配等东西,我不介意消费者是否需要等待更长时间或者更努力地工作。
是否有更快的实施,因为我只有一个消费者和单个生产者?
答案 0 :(得分:31)
嗯,确实没有太多的选择。让我来看看listed subclasses:
DelayQueue
,LinkedBlockingDeque
,PriorityBlockingQueue
和SynchronousQueue
都是针对需要额外功能的特殊情况而制作的;在这种情况下,它们没有意义。
仅留下ArrayBlockingQueue
和LinkedBlockingQueue
。如果您知道如何判断是否需要ArrayList
或LinkedList
,您可以自己回答这个问题。
请注意,在LinkedBlockingQueue
中,“每次插入时都会动态创建链接节点”;这可能会让你走向ArrayBlockingQueue
。
答案 1 :(得分:11)
您可以使用Java编写延迟敏感系统,但必须注意内存分配,线程之间传递的信息和锁定。您应该编写一些简单的测试来查看这些内容在服务器上花费的时间。 (它们根据操作系统和内存架构而有所不同)
如果这样做,您可以在99.99%的时间内获得稳定的操作时间。这不是正确的实时,但它可能足够接近。在交易系统中,使用Java代替C成本可能花费100英镑/天,但是用C / C ++而不是Java开发的成本可能远高于此。例如就它给我们的灵活性和它节省的错误数量而言。
你可以得到相同数量的抖动,你会在C程序中看到相同的抖动。有人称之为“类C”Java。
不幸的是,通过我工作的服务器上的ArrayBlockingQueue在Java中的线程之间传递一个对象需要大约8微秒,我建议你在服务器上测试它。一个简单的测试是在线程之间传递System.nanoTime()并查看它需要多长时间。
ArrayBlockingQueue有一个“功能”,它在每个添加的元素上创建一个对象(尽管没有太多理由这样做)所以如果你找到一个不这样做的标准实现,请告诉我。
答案 2 :(得分:5)
LinkedBlockingQueue
将具有O(1)插入成本。非常大的ArrayBlockingQueue
将具有O(1)插入成本,除非系统由于垃圾收集而被阻止;但是,它会在容量时阻止插入。
即使使用并发垃圾收集,我也不确定您是否应该使用托管语言编写实时系统。
答案 3 :(得分:4)
我同意Andrew Duffy的观点,即在java中实现这样一个受时间限制的系统可能是一个错误。无论如何,假设您已经与JVM结合,并且您不希望此队列正在运行(即,消费者可以处理负载),我认为您可能最好通过自定义实现服务,类似于ArrayBlockingQueue但是针对单个生产者/消费者场景进行了调整/优化。特别是,我喜欢在生产者方面旋转以等待空间而不是阻塞的概念。
我提到java.concurrent sources作为指导,并起草了这个算法......
对我来说看起来相当不错,但它完全没有经过考验,在实践中可能没有任何更快的速度(可能不是革命性的,但我确实自己动手了:-)。无论如何,我玩得很开心......你能找到一个缺陷吗?
伪代码:
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final E[] buffer;
private int head = 0
private int tail = 0;
public void put(E e) {
if (e == null) throw NullPointerException();
while (buffer[tail] != null) {
// spin, wait for space... hurry up, consumer!
// open Q: would a tight/empty loop be superior here?
Thread.sleep(1);
}
buffer[tail] = e;
tail = (tail + 1) % buffer.length;
if (count.getAndIncrement() == 0) {
sync(takeLock) { // this is pseudocode -- should be lock/try/finally/unlock
notEmpty.signal();
}
}
}
public E take() {
sync(takeLock) {
while (count.get() == 0) notEmpty.await();
}
E item = buffer[head];
buffer[head] = null;
count.decrement();
head = (head + 1) % buffer.length;
return item;
}
答案 4 :(得分:3)
如果时序要求严格,您可能需要在完全相同的硬件上进行广泛的基准测试,以确定最佳匹配。
如果我猜测,我会选择ArrayBlockingQueue
,因为基于数组的集合往往具有很好的引用位置。
答案 5 :(得分:3)
您可以考虑使用LinkedTransferQueue,它实现了“Slack的双重队列”,并且擅长匹配生产者和消费者。 有关详细信息,请参阅以下 Java 7 TransferQueue,Dual Synchronous Queues (.pdf)和LinkedTransferQueue source
答案 6 :(得分:2)
ArrayBlockingQueue可能会更快,因为您不需要在插入时创建链接节点。但请注意,ArrayBlockingQueue具有固定长度,如果您希望队列任意增大(至少为Integer.MAX_INT),则必须使用LinkedBlockingQueue(除非您要分配Integer.MAX_INT元素的数组)