Java - get方法的锁定获取

时间:2014-03-12 09:41:22

标签: java multithreading concurrency locking queue

我最近在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]。现在,我不确定在这种情况下锁定获取实际上是必要的......由于某些边缘情况编译器优化,它是否可以在没有锁定的情况下是非线程安全的?

3 个答案:

答案 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,您还可以将两个变量组合在一起。在您的示例中,它将是大小和队列。