我将此代码(Obfuscated)作为大型应用程序的一部分,并在NullPointerException
行上获得object.doSomething()
。由于我们刚刚检查了isEmpty()
调用并且没有其他线程轮询此队列,这怎么可能?还有其他线程添加到队列中;是否有可能并发添加永久搞砸队列?
我尝试阅读ArrayDeque
的源代码,并使用head == tail
作为isEmpty()
的检查。在添加head != tail
期间是否可能发生一些奇怪的碰撞,但head
指向null
?
private final Queue<Task> active = new ArrayDeque<Task>();
if (!this.active.isEmpty()) {
SomeType object = null;
object = this.active.poll();
object.doSomething();
}
答案 0 :(得分:3)
即使没有其他线程轮询,也可能有其他线程推送。
这意味着在并发访问中尾部可以被错误地修改,如果尾部被破坏,你可能永远不会到head == tail
,因此NullPointerException
。
正如@dacwe所述,文档明确指出您(或此混淆应用程序的开发人员)不应在并发环境中使用ArrayDeque
,这是并发可能存在的问题之一。
他们不是线程安全的;在没有外部同步的情况下,它们不支持多线程的并发访问。
如果您需要线程安全Queue
,可以使用LinkedBlockingQueue
,如果您需要Dequeue
,则可以使用LinkedBlockingDeque
。
<强>资源:强>
答案 1 :(得分:1)
如api中所述:
它们不是线程安全的; 在没有外部同步的情况下,它们不支持多线程的并发访问。
答案 2 :(得分:0)
当active.poll()
访问ArrayDequeue.doubleCapacity()
中回收的旧元素[]时,您可以考虑这种情况。
一个可能的时间表:
active.isEmpty()
返回false active.addLast()
,以便active
已满并且触发doubleCapacity()我的猜测是,您希望在队列不为空时避免轮询同步。为了避免因doubleCapacity()导致的争用,请确保为队列分配了足够大的容量,并且在调用addLast()时不会满。但是,根据实际实施情况,您可能需要考虑其他种族。
来自openJDK的以下来源附加了FYI。
public E pollFirst() {
int h = head;
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result == null)
return null;
elements[h] = null; // Must null out slot
head = (h + 1) & (elements.length - 1);
return result;
}
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);
System.arraycopy(elements, 0, a, r, p);
elements = a;
head = 0;
tail = n;
}