Volatile и ArrayBlockingQueue и, возможно, другие параллельные объекты


Я понимаю (или, по крайней мере, думаю, что понимаю;) ) принцип, лежащий в основе ключевого слова volatile. При просмотре источника ConcurrentHashMap можно увидеть, что все узлы и значения объявлены volatile, что имеет смысл, поскольку значение может быть записано/прочитано из более чем одного потока:

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
    ...
}

Однако, глядя в ArrayBlockingQueue источник, это простой массив, который обновляется/читается из нескольких потоков:

private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    notEmpty.signal();
}

Как гарантировать, что значение, вставленное в items[putIndex], будет видно из другого потока, при условии, что элемент внутри массива не является изменчивым (я знаю, что объявление самого массива не оказывает никакого влияния на сами элементы)? Не может ли другой поток содержать кэшированную копию массива?

Спасибо

2 2

2 ответа:

Обратите внимание, что enqueue составляет private. Ищите все вызовы к нему (offer(E), offer(E, long, TimeUnit), put(E)). Обратите внимание, что каждый из них выглядит следующим образом:

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // Do stuff.
        enqueue(e);
    } finally {
        lock.unlock();
    }
}
Таким образом, вы можете заключить, что каждый вызов enqueue защищен a lock.lock() ... lock.unlock() таким образом, вам не нужно volatile, потому что lock.lock/unlock также являются барьером памяти.

В моем понимании volatile не нужен, так как все реализации BlockingQueue уже имеют механизм блокировки в отличие от ConcurrentHashMap. Если вы посмотрите на открытые методы очереди, вы найдете ReentrantLock, который защищает для параллельного доступа.