我最近在Java中研究了一些并发类,例如PriorityBlockingQueue
,这里是相关的代码片段:
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
E result;
try {
result = size > 0 ? (E) queue[0] : null;
} finally {
lock.unlock();
}
return result;
}
考虑到队列[0]已设置或为空,我们考虑重写这一点(出于此问题的目的),因此变为简单return queue[0]
。现在,我不确定在这种情况下锁定获取实际上是必要的......由于某些边缘情况编译器优化,它是否可以在没有锁定的情况下是非线程安全的?
答案 0 :(得分:2)
锁定是为了确保调用PriorityBlockingQueue
上的方法的时间顺序的外部一致性。
在thread-offer
调用offer
之前说thread-peek
次来电peek
- 锁定确保thread-peek
被阻止,直到thread-offer
完成,指向添加的值thread-offer
,可以由thread-peak
返回 - 日志可能如下所示:
2013-03-12 10:40:00.000 [thread-offer] INFO offering value X
2013-03-12 10:40:00.001 [thread-peak] INFO peeking value
2013-03-12 10:40:00.002 [thread-offer] INFO offered value X
2013-03-12 10:40:00.003 [thread-peak] INFO peeked value X
peek
方法锁定的替代方法可以允许返回null
,尽管之前调用了offer
- 日志可能如下所示:< / p>
2013-03-12 10:50:00.000 [thread-offer] INFO offering value X
2013-03-12 10:50:00.001 [thread-peak] INFO peeking value
2013-03-12 10:50:00.002 [thread-peak] INFO peeked value null
2013-03-12 10:50:00.003 [thread-offer] INFO offered value X
这些日志有点人为,但希望能证明操作应该是原子的。
答案 1 :(得分:1)
考虑另一个同时调用remove()
的线程。如果peek()
未锁定,则可能发生这种情况:
peeker remover
------ -------
ret = queue[0]
return queue[0] <-- BUG
queue[0] <- queue[1]
return ret
你有不一致的地方;由于peeker可以与卸载程序同时执行,因此它不会看到remove操作的结果。它返回错误的结果。这意味着队列不再是线程安全的。
答案 2 :(得分:0)
您可以使用CopyOnWriteArrayList或查看该类的实现。如果修改了列表,则只有同步。但请注意,即使某些其他线程正在删除或添加元素,您也可以阅读该列表。您始终使用当前列表的副本。 PriorityBlockingQueue锁定每个公共方法。因此,如果您有许多线程,这可以阻止线程有效工作。
还要考虑这个实现(另请参阅AtomicInteger)。每个线程都可以使用PriorityQueue的本地副本,因此您不再需要同步。 AtomicReference不像锁定那么昂贵:
public class PriorityQueueUpdater {
private AtomicReference<PriorityQueue<String>> priorityQueueRef= new AtomicReference<> (null);
public PriorityQueue<String> getPriorityQueueRef() {
for (;;) {
PriorityQueue<String> current = priorityQueueRef.get();
PriorityQueue<String> next = priorityQueueRef.get();
if (priorityQueueRef.compareAndSet(current, next))
return current;
}
}
public void setPriorityQueue(PriorityQueue<String> priorityQueue) {
this.priorityQueueRef.set(priorityQueue);
}
}
PS:使用AtomicMarkableReference,您还可以将两个变量组合在一起。在您的示例中,它将是大小和队列。