Volatile 和 ArrayBlockingQueue 以及其他可能的并发对象

Volatile and ArrayBlockingQueue and perhaps other concurrent objects

我理解(或者至少我认为我理解;))volatile 关键字背后的原理。 查看 ConcurrentHashMap 源代码时,您可以看到所有节点和值都声明为 volatile,这是有道理的,因为值可以是来自多个线程的 written/read:

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 源代码,它是一个来自多个线程的 updated/read 普通数组:

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] 中的值将从另一个线程可见,前提是数组中的元素不是易失性的(我知道声明数组本身对元素本身)? 另一个线程不能保存数组的缓存副本吗?

谢谢

请注意 enqueueprivate。查找对它的所有调用 (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 的每次调用都受到 lock.lock() ... lock.unlock() 的保护,因此您不需要 volatile,因为 lock.lock/unlock也是内存障碍。

根据我的理解,不需要 volatile,因为所有 BlockingQueue 实现都已经具有锁定机制,这与 ConcurrentHashMap 不同。 如果您查看 Queue 的 public 方法,您会发现 ReentrantLock 可以保护并发访问。