2014-03-12 3 views
3

Недавно я изучил некоторые параллельные классы в Java, таких как PriorityBlockingQueue и вот соответствующий фрагмент кода:Java - блокировка приобретение по методу GET

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]. Теперь я не уверен, что в этом случае сборка на самом деле необходима ... Может ли это быть потокобезопасным без блокировки из-за некоторых оптимизаций компилятора в крайнем случае?

ответ

2

Замок предназначен для обеспечения внешней консистенции по отношению к порядку во времени, вызванному методами на PriorityBlockingQueue.

Say 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 - журналы могут выглядеть примерно так:

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 

Эти журналы немного надуманны, но, надеюсь, демонстрируют, что операции должны быть атомарными.

+0

Но очередь имеет длину, например. 10 и не сжимается ни одним из методов, не так ли? SImply call' queue [0] 'в очереди, которая имеет все элементы null, не будет вызывать никаких исключений – struggler

+0

И я предполагаю для этой цели, что' queue.length> 0' – struggler

+0

@struggler - прыгнул с самого начала с этим ответом, я изменил его, на то, что сейчас считаю правильным –

1

Рассмотрите другую тему, вызывающую remove() в то же время. Если peek() не заблокирован, это может произойти:

peeker    remover 
------    ------- 
        ret = queue[0] 
return queue[0] <-- BUG 
        queue[0] <- queue[1] 
        return ret 

У вас есть несоответствие; поскольку peeker может выполнять одновременно с удалением, он не видит результат операции удаления. Он возвращает неверный результат. Это означает, что очередь больше не защищена потоком.

+0

Я не понимаю, почему это ошибка: peek возвращает первый элемент очереди, что мы и хотим.Мы не можем гарантировать, когда мы вызываем peek(), что remove() вызывается и выполняется непосредственно перед блокировкой() –

+0

. За исключением того, что возвращается, на самом деле 'queue [1]'. – fge

+0

Нет, он возвращает текущую очередь [0], которая ранее находилась в очереди [1]. Если другой поток удаляет элементы, он не может обеспечить, что будет искать, когда запрашивает следующий элемент для обработки, даже с блокировкой. –

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 вы можете также комбинировать 2 переменные с одним щелчком. В вашем примере это будет размер и очередь.

Смежные вопросы