BlockingQueue的drainTo()方法的线程安全性

时间:2011-07-07 07:21:51

标签: java thread-safety blockingqueue

BlockingQueue的文档说批量操作不是线程安全的,尽管它没有明确提到方法drainTo()。

  

BlockingQueue实现是   线程安全的。所有排队方法   用原子方式实现它们的效果   内部锁或其他形式的   并发控制。但是,批量   收集操作addAll,   containsAll,retainAll和removeAll   不一定要执行   原则上除非另有说明   在实施中。就是这样   例如,对于addAll(c)   之后失败(抛出异常)   只添加c中的一些元素。

drainTo()方法的文档指定无法以线程安全的方式修改阻塞BlockingQueue元素的集合。但是,它没有提到任何关于drainTo()操作是线程安全的。

  

从中删除所有可用元素   这个队列并将它们添加到给定的队列中   采集。这个操作可能更多   效率比反复轮询这个   队列。遇到失败的时候   试图添加元素   集合c可能导致元素   既不在,也不在   集合时的关联   抛出异常。试图消耗   自己导致的队列   IllegalArgumentException异常。而且,   这个操作的行为是   如果指定的集合,则为undefined   在操作进入时修改   进展。

那么,drainTo()方法是否是线程安全的?换句话说,如果一个线程在阻塞队列上调用了drainTo()方法而另一个线程在同一个队列上调用了add()或put(),那么两个操作结束时队列的状态是否一致?

3 个答案:

答案 0 :(得分:16)

我认为你混淆了“线程安全”和“原子”这两个术语。它们并不代表同一件事。方法可以是线程安全的而不是原子的,并且可以是原子的(对于单个线程)而不是线程安全的。

线程安全是一个橡胶术语,如果不是循环的话很难定义。根据Goetz的说法,一个好的工作模型是,如果一个方法在多线程上下文中使用它是“正确的”,因为它在单线程上下文中运行,它是线程安全的。除非你有一个正式的规范来衡量,否则正确性是主观的。

相比之下,原子很容易定义。它只是意味着操作完全发生或根本不发生。

所以问题的答案是drainTo()是线程安全的,但不是原子的。它不是原子的,因为它可以通过排水中途抛出异常。但是,以模数表示,队列仍将处于一致状态,无论其他线程是否同时对队列执行操作。


(在上面的讨论中暗示BlockingQueue接口的具体实现正确地实现了接口。如果没有,则所有的注意都被关闭。)

答案 1 :(得分:5)

drainTo()是线程安全的,因为队列上同时发生的任何操作都不会改变结果,也不会破坏队列的状态。否则,该方法将毫无意义。

如果目标集合(添加结果的集合)执行“聪明”操作,则可能会遇到问题。但是,由于您通常将队列排放到只有一个线程可以访问的集合中,因此更多的是理论问题。

答案 2 :(得分:0)

偶然发现了这个问题,感觉就像添加了一个实施信息。

来自Java 8 source的PriorityBlockingQueue:

 /**
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     * @throws IllegalArgumentException      {@inheritDoc}
     */
    public int drainTo(Collection<? super E> c, int maxElements) {
        if (c == null)
            throw new NullPointerException();
        if (c == this)
            throw new IllegalArgumentException();
        if (maxElements <= 0)
            return 0;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            int n = Math.min(size, maxElements);
            for (int i = 0; i < n; i++) {
                c.add((E) queue[0]); // In this order, in case add() throws.
                dequeue();
            }
            return n;
        } finally {
            lock.unlock();
        }
    }

您会看到ReentrantLock用于锁定关键部分。方法poll()offer()也使用相同的锁。因此,在PriorityBlockingQueue情况下的BlockingQueue实现确实是 Blocking