目前在多线程环境中,我们使用LinkedList来保存数据。有时在日志中,我们在轮询链表时会收到NoSuchElementException。如果我们从linkedlist转到ConcurrentLinkedQueue实现,请帮助理解性能影响。
谢谢, 萨钦
答案 0 :(得分:8)
当你得到NoSuchElementException
时,这可能是因为没有正确同步。
例如:如果元素在列表中,然后尝试使用it.hasNext()
获取元素,则使用it.next()
进行检查。当元素在中间被删除时,这可能会失败,并且当您使用Collection API的同步版本时也会发生这种情况。
因此转移到ConcurrentLinkedQueue
无法真正解决您的问题。你可能没有得到例外,但是你必须准备好,即使你在它之前检查它是非空的,也会返回null
。 (这仍然是相同的错误,但实现方式不同。)只要您的代码中没有正确的同步,并且在SAME同步范围内检查空白和元素检索,就是这样。
您很有可能在NoSuchElementException
之后交换新的NullPointerException
。
这可能不是直接解决您的性能问题的答案,但在LinkedList中将NoSuchElementException
作为移至ConcurrentLinkedQueue
的理由听起来有点奇怪。
修改强>
一些用于破坏实现的伪代码:
//list is a LinkedList
if(!list.isEmpty()) {
... list.getFirst()
}
正确同步的一些伪代码:
//list is a LinkedList
synchronized(list) {
if(!list.isEmpty()) {
... list.getFirst()
}
}
“破坏”同步的一些代码(不按预期工作)。 这可能是直接从LinkedList切换到CLQ的结果,希望能够自己摆脱同步。
//queue is instance of CLQ
if(!queue.isEmpty()) { // Does not really make sense, because ...
... queue.poll() //May return null! Good chance for NPE here!
}
一些正确的代码:
//queue is instance of CLQ
element = queue.poll();
if(element != null) {
...
}
或
//queue is instance of CLQ
synchronized(queue) {
if(!queue.isEmpty()) {
... queue.poll() //is not null
}
}
答案 1 :(得分:7)
ConcurrentLinkedQueue
[是]一个无限制的,线程安全的,FIFO排序的队列。它使用链接结构,类似于我们在第13.2.2节中看到的跳过列表的基础,以及第13.1.1节中的哈希表溢出链接。我们注意到链接结构的主要吸引力之一是指针重排实现的插入和移除操作在恒定时间内执行。这使得它们作为队列实现特别有用,其中在结构末端的单元格上总是需要这些操作,即,不需要使用链接结构的慢顺序搜索来定位单元格。
ConcurrentLinkedQueue
使用基于CAS的无等待算法,该算法保证任何线程始终可以完成其当前操作,无论其他线程访问队列的状态如何。它以恒定时间执行队列插入和删除操作,但需要线性时间来执行size
。这是因为该算法依赖于线程之间的协作来进行插入和删除,它不会跟踪队列大小,并且必须迭代队列以在需要时对其进行计算。
来自Java Generics and Collections,ch。 14.2。
请注意ConcurrentLinkedQueue
没有实现List
接口,因此只有当LinkedList
纯粹作为队列使用时,它才能替代ConcurrentLinkedQueue
。在这种情况下,{{1}}显然是更好的选择。除非经常查询其大小,否则不应存在大的性能问题。但作为免责声明,如果您在自己的具体环境和程序中进行测量,则只能确定性能。