JDK 1.8 ArrayBlockingQueue 源碼閱讀(二)獲取

在上一節,介紹了ArrayBlockingQueue的添加元素的方法,本節,結合源碼給大家介紹一下獲取元素的方法。

獲取元素的方法有下述幾種

E poll() 立刻返回,如果隊列爲空,返回null

E take() 如果隊列不爲空,返回隊首元素,否則阻塞到隊列不爲空

E poll(long timeout, TimeUnit unit) 等待timeout 時間的poll

E peek() 獲得隊首的元素,並不將這個元素彈出


分別看一下這幾個方法的源碼

1.E poll()

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }


此方法先獲得lock,如果lock獲得成功,會先去判斷count 的值,如果這個值爲0,那就說明此隊列爲空,立刻返回null

2.E take()

 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
take 方法和poll方法的區別就在於,如果count爲0,那麼在notEmpty上等待,直到被喚醒,這裏有個需要遵循的原則,await/wait方法調用的時候,一定要確保本線程持有依賴的狀態(這裏邊爲count)的鎖,並且被喚醒之後,依舊要去檢查依賴條件的狀態,這是《JAVA 併發編程實戰》推薦的使用方式,原因此處不再多說。

3.E poll(long timeout, TimeUnit unit)

我們看到此方法傳遞了一個超時時間,也就是在條件隊列上等待相應的時間,如果等待超過這個時間,那就返回空。

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }


4.E peek()  只是獲得元素,並不將元素從隊列中刪除,這個方法很簡單,只是將相應位置的元素返回,不對隊列進行任何操作
    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return itemAt(takeIndex); // null when queue is empty
        } finally {
            lock.unlock();
        }
    }

5. 再來分析一下出隊的方法,這個方法是幾個獲取方法的核心

private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }
看這個方法,感覺不難,只是將takeIndex的內容取出來,並將該位置的元素設置爲空,然後檢查一下隊列是不是空了,然後喚醒在notFull條件等待的線程,最後操作一下迭代器

6.remove ,

 /**
     * Removes a single instance of the specified element from this queue,
     * if it is present.  More formally, removes an element {@code e} such
     * that {@code o.equals(e)}, if this queue contains one or more such
     * elements.
     * Returns {@code true} if this queue contained the specified element
     * (or equivalently, if this queue changed as a result of the call).
     *
     * <p>Removal of interior elements in circular array based queues
     * is an intrinsically slow and disruptive operation, so should
     * be undertaken only in exceptional circumstances, ideally
     * only when the queue is known not to be accessible by other
     * threads.
     *
     * @param o element to be removed from this queue, if present
     * @return {@code true} if this queue changed as a result of the call
     */
    public boolean remove(Object o) {
        if (o == null) return false;
        final Object[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count > 0) {
                final int putIndex = this.putIndex;
                int i = takeIndex;
                do {
                    if (o.equals(items[i])) {
                        removeAt(i);
                        return true;
                    }
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

這個remove方法,首先判斷對列是否有元素,然後從takeIndex 開始調用equals 方法,判斷是否是相同的元素,如果相同,會去調removeAt(i);

我們再看removeAt這個方法,ArrayBlockingQueue支持刪除任意位置上的元素

void removeAt(final int removeIndex) {
        // assert lock.getHoldCount() == 1;
        // assert items[removeIndex] != null;
        // assert removeIndex >= 0 && removeIndex < items.length;
        final Object[] items = this.items;
        if (removeIndex == takeIndex) {
            // removing front item; just advance
            items[takeIndex] = null;
            if (++takeIndex == items.length)
                takeIndex = 0;
            count--;
            if (itrs != null)
                itrs.elementDequeued();
        } else {
            // an "interior" remove

            // slide over all others up through putIndex.
            final int putIndex = this.putIndex;
            for (int i = removeIndex;;) {
                int next = i + 1;
                if (next == items.length)
                    next = 0;
                if (next != putIndex) {
                    items[i] = items[next];
                    i = next;
                } else {
                    items[i] = null;
                    this.putIndex = i;
                    break;
                }
            }
            count--;
            if (itrs != null)
                itrs.removedAt(removeIndex);
        }
        notFull.signal();
    }
這個方法,首先判斷了需要刪除的元素是否是takeIndex所指向的元素,如果是,只需要將這個元素設置爲null,並修改一下takeIndex 就好,但是如果不是,就需要將後面的元素全都往前移動一個位置,並且,迭代器也將固定位置的元素刪除。

看到現在,貌似沒有發現迭代器有什麼明顯的作用,暫且先認爲他是這個ArrayBlockingQueue的一個鏈表實現的副本,等下節我們再研究

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章