有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

LinkedBlockingQueue中的java addAll()是线程安全的(如果不是,解决方案是什么)?

引用文件:

"BlockingQueue implementations are thread-safe. All queuing methods achieve their effects atomically using internal locks or other forms of concurrency control. However, the bulk Collection operations addAll, containsAll, retainAll and removeAll are not necessarily performed atomically unless specified otherwise in an implementation. So it is possible, for example, for addAll(c) to fail (throwing an exception) after adding only some of the elements in c."

因为在LinkedBlockingQueue.addAll()操作的描述中没有写任何特别的东西,所以我不得不假设这不是线程安全的

您同意我的观点吗?要保证通过addAll()添加的所有元素都是连续的(即添加在一起),唯一的解决方案是每次修改队列时使用Lock(使用addtake操作)?例如:

BlockingQueue<T> queue = new LinkedBlockingQueue<>();
Lock lock = new ReentrantLock();

//somewhere, some thread...
lock.lock();
queue.addAll(someCollection);
lock.unlock();

//somewhere else, (maybe) some other thread...
lock.lock();
queue.take();
lock.unlock();

重要更新: 哇,在前面的示例中,没有人看到一个大错误:因为take()是一个阻塞操作,而且为了向队列中添加元素需要锁,一旦队列为空,程序就会进入死锁状态:由于take()拥有锁,所以写入程序无法写入,同时take()将处于阻塞状态,直到队列中写入了某些内容(由于前面的原因,它无法发生)<有什么想法吗我认为最明显的是移除take()周围的锁,但是addAll()所需的原子性可能无法保证


共 (2) 个答案

  1. # 1 楼答案

    addAll仍然是线程安全的,它只是不提供原子性。 因此,这取决于您的用例/期望是什么

    如果在没有显式锁定的情况下使用addAll,那么如果其他线程尝试写入队列(添加新元素),则添加元素的顺序无法保证,它们可能会混合。如果是问题,则需要锁定。但是addAll仍然是线程安全的,不会损坏队列

    但通常,队列用于提供多个读写器之间的通信方式,可能不需要严格保存插入顺序

    现在的主要问题是,如果队列已满,Add方法会抛出异常,因此AdDALL操作会在中间崩溃,而不知道添加了哪些元素。

    如果您的用例允许等待空间来插入元素,那么您应该使用put-in-a循环

    for (E e: someCollection) queue.put(e);
    

    这将阻塞,直到有空间添加其他元素为止

    手动锁定很棘手,因为在访问队列时,您必须始终记住添加锁定,这很容易出错。因此,如果您确实需要原子性,请编写一个包装器类,该类实现BlockingQUeue接口,但在调用底层操作之前使用锁定

  2. # 2 楼答案

    我相信你把线程安全的和原子的搞混了。据我所知,批量操作是线程安全的,尽管不是原子操作

    我认为您不需要使用外部ReentrantLock来使BlockingQueue成为线程安全的。实际上,addAll()是通过对给定的Collection进行迭代并为集合的每个元素在队列上调用add()来实现的。因为add()是线程安全的,所以您不需要同步任何东西

    当javadocs这样说时:

    So it is possible, for example, for addAll(c) to fail (throwing an exception) after adding only some of the elements in c.

    这意味着当addAll(c)返回时,可能只添加了给定集合c的某些元素。但是,这并不意味着您需要锁定队列来调用take()或任何其他操作

    编辑:

    根据您的用例,您可以按照您的建议使用锁,尽管我会将其设置为BlockingQueue实现的内部,这样调用方类就不需要遵循队列/解锁模式中的lock/call\u some\u method\u。在使用锁时,我会更加小心,即在try/finally块中使用它:

    public class MyQueue<T> extends LinkedBlockingQueue<T> {
    
        private final Lock lock = new ReentrantLock();
    
        @Override
        public boolean addAll(Collection<T> c) {
            boolean r = false;
            try {
                this.lock.lock();
                r = super.addAll(c);
            } finally {
                this.lock.unlock();
            }
            return r;
        }
    
        @Override
        public void add(T e) {
            try {
                this.lock.lock();
                super.add(e);
            } finally {
                this.lock.unlock();
            }
        }
    
        // You don't need to lock on take(), since
        // it preserves the order in which elements
        // are inserted and is already thread-safe
    }