ArrayDeque不为空,但对poll方法返回null

时间:2012-04-12 15:20:57

标签: java queue

我将此代码(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();
}

3 个答案:

答案 0 :(得分:3)

即使没有其他线程轮询,也可能有其他线程推送。

这意味着在并发访问中尾部可以被错误地修改,如果尾部被破坏,你可能永远不会到head == tail,因此NullPointerException

正如@dacwe所述,文档明确指出您(或此混淆应用程序的开发人员)不应在并发环境中使用ArrayDeque,这是并发可能存在的问题之一。


  

他们不是线程安全的;在没有外部同步的情况下,它们不支持多线程的并发访问。


如果您需要线程安全Queue,可以使用LinkedBlockingQueue,如果您需要Dequeue,则可以使用LinkedBlockingDeque


<强>资源:

答案 1 :(得分:1)

api中所述:

  

它们不是线程安全的; 在没有外部同步的情况下,它们不支持多线程的并发访问。

答案 2 :(得分:0)

active.poll()访问ArrayDequeue.doubleCapacity()中回收的旧元素[]时,您可以考虑这种情况。

一个可能的时间表:

  1. 轮询线程检出active.isEmpty()返回false
  2. 轮询线程调用```active.pollFirst()来访问非原子元素[]
  3. 一个或多个其他线程以突发方式调用active.addLast(),以便active已满并且触发doubleCapacity()
  4. 在doubleCapacity()中,元素[]被新分配的数组替换,以便GC回收旧元素[]
  5. 轮询线程现在引用回收的元素[],并且可能为空。
  6. 我的猜测是,您希望在队列不为空时避免轮询同步。为了避免因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;
    }