BlockingQueue的文档说批量操作不是线程安全的,尽管它没有明确提到方法drainTo()。
BlockingQueue实现是 线程安全的。所有排队方法 用原子方式实现它们的效果 内部锁或其他形式的 并发控制。但是,批量 收集操作addAll, containsAll,retainAll和removeAll 不一定要执行 原则上除非另有说明 在实施中。就是这样 例如,对于addAll(c) 之后失败(抛出异常) 只添加c中的一些元素。
drainTo()方法的文档指定无法以线程安全的方式修改阻塞BlockingQueue元素的集合。但是,它没有提到任何关于drainTo()操作是线程安全的。
从中删除所有可用元素 这个队列并将它们添加到给定的队列中 采集。这个操作可能更多 效率比反复轮询这个 队列。遇到失败的时候 试图添加元素 集合c可能导致元素 既不在,也不在 集合时的关联 抛出异常。试图消耗 自己导致的队列 IllegalArgumentException异常。而且, 这个操作的行为是 如果指定的集合,则为undefined 在操作进入时修改 进展。
那么,drainTo()方法是否是线程安全的?换句话说,如果一个线程在阻塞队列上调用了drainTo()方法而另一个线程在同一个队列上调用了add()或put(),那么两个操作结束时队列的状态是否一致?
答案 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 !