【Java數據結構及算法實戰】系列009:Java隊列03——數組實現的阻塞隊列ArrayBlockingQueue

顧名思義,ArrayBlockingQueue是基於數組實現的有界阻塞隊列。該隊列對元素進行FIFO排序。隊列的首元素是在該隊列中駐留時間最長的元素。隊列的尾部是在該隊列中停留時間最短的元素。新的元素被插入到隊列的尾部,隊列檢索操作獲取隊列頭部的元素。

 

 

 

ArrayBlockingQueue是一個經典的“有界緩衝區(bounded buffer)”,其中內部包含了一個固定大小的數組,用於承載包含生產者插入的和消費者提取的元素。ArrayBlockingQueue的容量一旦創建,不可更改。試圖將一個元素放入一個滿隊列將導致操作阻塞;試圖從空隊列中取出一個元素也同樣會阻塞。

 

 

 

ArrayBlockingQueue支持排序的可選公平策略,用於等待生產者和消費者線程。默認情況下,不保證此順序。然而,一個由公平性設置爲true構造的隊列允許線程以FIFO順序訪問。公平性一般會降低吞吐量,但可以減少可變性,避免線程餓死。

 

 

 

ArrayBlockingQueue類及其迭代器實現了CollectionIterator接口的所有可選方法。ArrayBlockingQueueJava Collections Framework的一個成員。

 

 

 

 

 

1.   ArrayBlockingQueue的聲明

 

ArrayBlockingQueue的接口和繼承關係如下

 

 

 

public class ArrayBlockingQueue<E> extends AbstractQueue<E>

 

        implements BlockingQueue<E>, java.io.Serializable {

 

 

 

   …

 

}

 

 

 

 

 

完整的接口繼承關係如下圖所示。

 

 

 

 

 

 

 

 

 

 

 

 

從上述代碼可以看出,ArrayBlockingQueue既實現了BlockingQueue<E>java.io.Serializable接口,又繼承了java.util.AbstractQueue<E>。其中,AbstractQueueQueue接口的抽象類,核心代碼如下。

 

 

 

 

 

package java.util;

 

 

 

public abstract class AbstractQueue<E>

 

    extends AbstractCollection<E>

 

    implements Queue<E> {

 

 

 

    protected AbstractQueue() {

 

    }

 

 

 

    public boolean add(E e) {

 

        if (offer(e))

 

            return true;

 

        else

 

            throw new IllegalStateException("Queue full");

 

    }

 

 

 

    public E remove() {

 

        E x = poll();

 

        if (x != null)

 

            return x;

 

        else

 

            throw new NoSuchElementException();

 

    }

 

 

 

    public E element() {

 

        E x = peek();

 

        if (x != null)

 

            return x;

 

        else

 

            throw new NoSuchElementException();

 

    }

 

 

 

    public void clear() {

 

        while (poll() != null)

 

            ;

 

    }

 

 

 

    public boolean addAll(Collection<? extends E> c) {

 

        if (c == null)

 

            throw new NullPointerException();

 

        if (c == this)

 

            throw new IllegalArgumentException();

 

        boolean modified = false;

 

        for (E e : c)

 

            if (add(e))

 

                modified = true;

 

        return modified;

 

    }

 

 

 

}

 

 

 

 

 

2.   ArrayBlockingQueue的成員變量和構造函數

 

 

 

 

 

以下是ArrayBlockingQueue的構造函數和成員變量。

 

 

 

    // 元素數組

 

    final Object[] items;

 

 

 

    // 消費索引,用於takepollpeekremove操作

 

    int takeIndex;

 

 

 

    // 生產索引,用於putofferadd操作

 

    int putIndex;

 

 

 

    // 隊列中的元素個數

 

    int count;

 

 

 

    /*

 

     * 使用經典的雙條件算法(two-condition algorithm)實現併發控制

 

     */

 

 

 

    // 操作數組確保原子性的鎖

 

    final ReentrantLock lock;

 

 

 

    // 數組非空,喚醒消費者

 

    private final Condition notEmpty;

 

 

 

    // 數組非滿,喚醒生產者

 

private final Condition notFull;

 

 

 

// 迭代器狀態

 

transient Itrs itrs;

 

 

 

public ArrayBlockingQueue(int capacity) {

 

        this(capacityfalse);

 

    }

 

 

 

    public ArrayBlockingQueue(int capacityboolean fair) {

 

        if (capacity <= 0)

 

            throw new IllegalArgumentException();

 

        this.items = new Object[capacity];

 

        lock = new ReentrantLock(fair);

 

        notEmpty = lock.newCondition();

 

        notFull =  lock.newCondition();

 

    }

 

 

 

    public ArrayBlockingQueue(int capacityboolean fair,

 

                              Collection<? extends E> c) {

 

        this(capacityfair);

 

 

 

        final ReentrantLock lock = this.lock;

 

        lock.lock(); // 只鎖可見,不互斥

 

        try {

 

            final Object[] items = this.items;

 

            int i = 0;

 

            try {

 

                for (E e : c)

 

                    items[i++] = Objects.requireNonNull(e);

 

            } catch (ArrayIndexOutOfBoundsException ex) {

 

                throw new IllegalArgumentException();

 

            }

 

            count = i;

 

            putIndex = (i == capacity) ? 0 : i;

 

        } finally {

 

            lock.unlock();  // 解鎖

 

        }

 

}

 

 

 

 

 

從上述代碼可以看出,構造函數有三種。構造函數中的參數含義如下

 

 

 

l  capacity用於設置隊列容量

 

l  fair用於設置訪問策略,如果爲true,則對線程的訪問在插入或移除時被阻塞,則按FIFO順序處理;如果爲false,則訪問順序未指定

 

l  c用於設置最初包含給定集合的元素,按集合迭代器的遍歷順序添加

 

 

 

類成員items是一個數組,用於存儲隊列中的元素。關鍵字final指明瞭,當ArrayBlockingQueue構造完成之後,通過new Object[capacity]的方式初始化items數組完成後,則後續items的容量將不再變化。

 

 

 

訪問策略是通過ReentrantLock來實現的。通過兩個加鎖條件notEmptynotFull來實現併發控制。這是典型的雙條件算法(two-condition algorithm)。

 

 

 

ArrayBlockingQueue生產則增加putIndex,消費則增加takeIndex

 

 

 

Itrs用於記錄當前活動迭代器的共享狀態,如果已知不存在任何迭代器,則爲null 允許隊列操作更新迭代器狀態。迭代器狀態不是本節的重點,不再深入探討。

 

3.   ArrayBlockingQueue的核心方法

 

以下對ArrayBlockingQueue常用核心方法的實現原理進行解釋。

 

 

 

 

 

3.1.     offer(e)

 

執行offer(e)方法後有兩種結果

 

 

 

l  隊列未滿時,返回 true

 

l  隊列滿時,返回 false

 

 

 

ArrayBlockingQueueoffer (e)方法源碼如下:

 

 

 

public boolean offer(E e) {

 

        Objects.requireNonNull(e);

 

        final ReentrantLock lock = this.lock;

 

        lock.lock(); // 加鎖

 

        try {

 

            if (count == items.length)

 

                return false;

 

            else {

 

                enqueue(e); // 入隊

 

                return true;

 

            }

 

        } finally {

 

            lock.unlock();  // 解鎖

 

        }

 

}

 

 

 

 

 

從上面代碼可以看出,執行offer(e)方法時,分爲以下幾個步驟:

 

 

 

l  爲了確保併發操作的安全先做了加鎖處理。

 

l  而後判斷count是否與數組items的長度一致,如果一致則證明隊列已經滿了,直接返回false;否則執行enqueue(e)方法做元素的入隊,並返回true

 

l  最後解鎖。

 

 

 

enqueue(e)方法源碼如下:

 

 

 

 

 

private void enqueue(E e) {

 

        final Object[] items = this.items;

 

        items[putIndex] = e;

 

        if (++putIndex == items.lengthputIndex = 0;

 

        count++;

 

        notEmpty.signal(); // 喚醒等待中的線程

 

}

 

 

 

上面代碼比較簡單,在當前索引(putIndex)位置放置待入隊的元素,而後putIndexcount分別遞增,並通過signal()方法喚醒等待中的線程。其中一個注意點是,當putIndex 等於數組items長度時,putIndex置爲0

 

 

 

思考:當putIndex 等於數組items長度時,putIndex爲什麼置爲0呢?

 

 

 

3.2.     put(e)

 

執行put(e)方法後有兩種結果:

 

      

 

l  隊列未滿時,直接插入沒有返回值

 

l  隊列滿時,會阻塞等待,一直等到隊列未滿時再插入

 

 

 

ArrayBlockingQueueput (e)方法源碼如下:

 

 

 

public void put(E ethrows InterruptedException {

 

        Objects.requireNonNull(e);

 

        final ReentrantLock lock = this.lock;

 

        lock.lockInterruptibly();  // 獲取鎖

 

        try {

 

            while (count == items.length)

 

                notFull.await();  // 使線程等待

 

            enqueue(e);  // 入隊

 

        } finally {

 

            lock.unlock();  // 解鎖

 

        }

 

    }

 

 

 

從上面代碼可以看出,put(e)方法的實現,分爲以下幾個步驟:

 

 

 

l  先是要獲取鎖。

 

l  而後判斷count是否與數組items的長度一致,如果一致則證明隊列已經滿了,就等待;否則執行enqueue(e)方法做元素的入隊。

 

l  最後解鎖。

 

 

 

3.3.     offer(e,time,unit)

 

offer(e,time,unit)方法與offer(e)方法不同之處在於,前者加入了等待機制。設定等待的時間,如果在指定時間內還不能往隊列中插入數據則返回false。執行offer(e,time,unit)方法有兩種結果:

 

      

 

l  隊列未滿時,返回 true

 

l  隊列滿時,會阻塞等待,如果在指定時間內還不能往隊列中插入數據則返回 false

 

 

 

ArrayBlockingQueueput (e)方法源碼如下:

 

 

 

public boolean offer(E elong timeout, TimeUnit unit)

 

        throws InterruptedException {

 

 

 

        Objects.requireNonNull(e);

 

        long nanos = unit.toNanos(timeout);

 

        final ReentrantLock lock = this.lock;

 

        lock.lockInterruptibly();  // 獲取鎖

 

        try {

 

            while (count == items.length) {

 

                if (nanos <= 0L)

 

                    return false;

 

                nanos = notFull.awaitNanos(nanos);  // 使線程等待指定的時間

 

            }

 

            enqueue(e);

 

            return true;

 

        } finally {

 

            lock.unlock();  // 解鎖

 

        }

 

    }

 

 

 

從上面代碼可以看出,offer(e,time,unit)方法的實現,分爲以下幾個步驟:

 

 

 

l  先是要獲取鎖。

 

l  而後判斷count是否與數組items的長度一致,如果一致則證明隊列已經滿了,就等待;否則執行enqueue(e)方法做元素的入隊。

 

l  最後解鎖。

 

 

 

3.4.     add(e)

 

執行add(e)方法後有兩種結果

 

 

 

l  隊列未滿時,返回 true

 

l  隊列滿時,則拋出異常

 

 

 

ArrayBlockingQueueadd(e)方法源碼如下:

 

 

 

    public boolean add(E e) {

 

        return super.add(e);

 

    }

 

 

 

 

 

從上面代碼可以看出,add(e)方法的實現,直接是調用了父類AbstractQueueadd(e)方法。而AbstractQueueadd(e)方法源碼如下:

 

 

 

 

 

public boolean add(E e) {

 

        if (offer(e))

 

            return true;

 

        else

 

            throw new IllegalStateException("Queue full");

 

}

 

 

 

 

 

從上面代碼可以看出,add(e)方法又調用了offer(e)方法。offer(e)方法此處不再贅述。

 

 

 

 

 

 

 

3.5.     poll ()

 

執行poll ()方法後有兩種結果:

 

 

 

l  隊列不爲空時,返回隊首值並移除

 

l  隊列爲空時,返回 null

 

 

 

 

 

ArrayBlockingQueuepoll()方法源碼如下:

 

 

 

public E poll() {

 

        final ReentrantLock lock = this.lock;

 

        lock.lock();  // 加鎖

 

        try {

 

            return (count == 0) ? null : dequeue(); // 出隊

 

        } finally {

 

            lock.unlock();  // 解鎖

 

        }

 

    }

 

 

 

從上面代碼可以看出,執行poll()方法時,分爲以下幾個步驟:

 

 

 

l  爲了確保併發操作的安全先做了加鎖處理。

 

l  而後判斷count是否等於0,如果等於0則證明隊列爲空,直接返回null;否則執行dequeue()方法做元素的出隊。

 

l  最後解鎖。

 

 

 

dequeue()方法源碼如下:

 

 

 

 

 

private E dequeue() {

 

        final Object[] items = this.items;

 

        @SuppressWarnings("unchecked")

 

        E e = (E) items[takeIndex];

 

        items[takeIndex] = null;  // 刪除數據

 

        if (++takeIndex == items.lengthtakeIndex = 0;

 

        count--;

 

        if (itrs != null)

 

            itrs.elementDequeued();

 

        notFull.signal(); // 喚醒等待中的線程

 

        return e;

 

}

 

 

 

上面代碼比較簡單,在當前索引(takeIndex)位置取出待出隊的元素並刪除隊列中的元素,而後takeIndex遞增count遞減,並通過signal()方法喚醒等待中的線程。其中一個注意點是,當takeIndex等於數組items長度時,takeIndex置爲0

 

 

 

3.6.     take()

 

執行take()方法後有兩種結果:

 

 

 

l  隊列不爲空時,返回隊首值並移除

 

l  隊列爲空時,會阻塞等待,一直等到隊列不爲空時再返回隊首值

 

 

 

ArrayBlockingQueuetake ()方法源碼如下:

 

 

 

public E take() throws InterruptedException {

 

        final ReentrantLock lock = this.lock;

 

        lock.lockInterruptibly();  // 獲取鎖

 

        try {

 

            while (count == 0)

 

                notEmpty.await(); // 使線程等待

 

            return dequeue();  // 出隊

 

        } finally {

 

            lock.unlock();  // 解鎖

 

        }

 

    }

 

 

 

從上面代碼可以看出,執行take()方法時,分爲以下幾個步驟:

 

 

 

l  先是要獲取鎖。

 

l  而後判斷count是否等於0,如果等於0則證明隊列爲空,會阻塞等待;否則執行dequeue()方法做元素的出隊。

 

l  最後解鎖。

 

 

 

dequeue()方法此處不再贅述。

 

 

 

3.7.     poll(time,unit)

 

poll(time,unit)方法與poll()方法不同之處在於,前者加入了等待機制。設定等待的時間,如果在指定時間內隊列還爲空,則返回null。執行poll(time,unit)方法後有兩種結果:

 

 

 

l  隊列不爲空時,返回隊首值並移除

 

l  隊列爲空時,會阻塞等待,如果在指定時間內隊列還爲空則返回 null

 

 

 

ArrayBlockingQueuepoll(time,unit)方法源碼如下:

 

 

 

public E poll(long timeout, TimeUnit unitthrows InterruptedException {

 

        long nanos = unit.toNanos(timeout);

 

        final ReentrantLock lock = this.lock;

 

        lock.lockInterruptibly();  // 獲取鎖

 

        try {

 

            while (count == 0) {

 

                if (nanos <= 0L)

 

                    return null;

 

                nanos = notEmpty.awaitNanos(nanos); // 使線程等待指定的時間

 

            }

 

            return dequeue();  // 出隊

 

        } finally {

 

            lock.unlock();  // 解鎖

 

        }

 

    }

 

從上面代碼可以看出,執行poll(time,unit)方法時,分爲以下幾個步驟:

 

 

 

l  先是要獲取鎖。

 

l  而後判斷count是否等於0,如果等於0則證明隊列爲空,會阻塞等待;否則執行dequeue()方法做元素的出隊。

 

l  最後解鎖。

 

 

 

dequeue()方法此處不再贅述。

 

 

 

 

 

3.8.     remove()

 

執行remove()方法後有兩種結果:

 

 

 

l  隊列不爲空時,返回隊首值並移除

 

l  隊列爲空時,拋出異常

 

 

 

ArrayBlockingQueueremove()方法其實是調用了父類AbstractQueueremove()方法,源碼如下:

 

 

 

public E remove() {

 

        E x = poll();

 

        if (x != null)

 

            return x;

 

        else

 

            throw new NoSuchElementException();

 

}

 

 

 

從上面代碼可以看出,remove()直接調用了poll()方法。如果poll()方法返回結果爲null,則拋出NoSuchElementException異常。

 

 

 

poll()方法此處不再贅述。

 

 

 

3.9.     peek()

 

執行peek()方法後有兩種結果:

 

 

 

l  隊列不爲空時,返回隊首值但不移除

 

l  隊列爲空時,返回null

 

 

 

 

 

peek()方法源碼如下:

 

 

 

public E peek() {

 

        final ReentrantLock lock = this.lock;

 

        lock.lock(); // 加鎖

 

        try {

 

            return itemAt(takeIndex); // 空則返回null

 

        } finally {

 

            lock.unlock();  // 解鎖

 

        }

 

}

 

 

 

final E itemAt(int i) {

 

        return (E) items[i];

 

}

 

 

 

從上面代碼可以看出,peek()方法比較簡單,直接就是獲取了數組裏面的索引爲takeIndex的元素。

 

 

 

3.10.            element()

 

執行element()方法後有兩種結果:

 

 

 

l  隊列不爲空時,返回隊首值但不移除

 

l  隊列爲空時,拋出異常

 

 

 

 

 

element()方法其實是調用了父類AbstractQueueelement()方法,源碼如下:

 

 

 

public E element() {

 

        E x = peek();

 

        if (x != null)

 

            return x;

 

        else

 

            throw new NoSuchElementException();

 

}

 

 

 

從上面代碼可以看出,執行element()方法時,先是獲取peek()方法的結果,如果結果是null,則拋出NoSuchElementException異常。

 

 

 

 

 

4.   ArrayBlockingQueue的單元測試

 

 

 

ArrayBlockingQueue的單元測試如下:

 

 

 

 

 

package com.waylau.java.demo.datastructure;

 

 

 

import static org.junit.jupiter.api.Assertions.assertEquals;

 

import static org.junit.jupiter.api.Assertions.assertFalse;

 

import static org.junit.jupiter.api.Assertions.assertNotNull;

 

import static org.junit.jupiter.api.Assertions.assertNull;

 

import static org.junit.jupiter.api.Assertions.assertThrows;

 

import static org.junit.jupiter.api.Assertions.assertTrue;

 

 

 

import java.util.NoSuchElementException;

 

import java.util.concurrent.ArrayBlockingQueue;

 

import java.util.concurrent.BlockingQueue;

 

import java.util.concurrent.TimeUnit;

 

 

 

import org.junit.jupiter.api.Test;

 

 

 

/**

 

 * ArrayBlockingQueue Tests

 

 *

 

 * @since 1.0.0 202053

 

 * @author <a href="https://waylau.com">Way Lau</a>

 

 */

 

class ArrayBlockingQueueTests {

 

    @Test

 

    void testOffer() {

 

        // 初始化隊列

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列未滿時,返回 true

 

        boolean resultNotFull = queue.offer("Java");

 

        assertTrue(resultNotFull);

 

 

 

        // 測試隊列滿則,返回 false

 

        queue.offer("C");

 

        queue.offer("Python");

 

        boolean resultFull = queue.offer("C++");

 

        assertFalse(resultFull);

 

    }

 

 

 

    @Test

 

    void testPut() throws InterruptedException {

 

        // 初始化隊列

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列未滿時,直接插入沒有返回值;

 

        queue.put("Java");

 

 

 

        // 測試隊列滿則, 會阻塞等待,一直等到隊列未滿時再插入。

 

        queue.put("C");

 

        queue.put("Python");

 

        queue.put("C++");  // 阻塞等待

 

    }

 

 

 

    @Test

 

    void testOfferTime() throws InterruptedException {

 

        // 初始化隊列

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列未滿時,返回 true

 

        boolean resultNotFull = queue.offer("Java", 5, TimeUnit.SECONDS);

 

        assertTrue(resultNotFull);

 

 

 

        // 測試隊列滿則,返回 false

 

        queue.offer("C");

 

        queue.offer("Python");

 

        boolean resultFull = queue.offer("C++", 5, TimeUnit.SECONDS); // 5

 

        assertFalse(resultFull);

 

    }

 

 

 

    @Test

 

    void testAdd() {

 

        // 初始化隊列

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列未滿時,返回 true

 

        boolean resultNotFull = queue.add("Java");

 

        assertTrue(resultNotFull);

 

 

 

        // 測試隊列滿則拋出異常

 

        queue.add("C");

 

        queue.add("Python");

 

 

 

        Throwable excpetion = assertThrows(IllegalStateException.class, () -> {

 

            queue.add("C++");// 拋異常

 

        });

 

 

 

        assertEquals("Queue full"excpetion.getMessage());

 

    }

 

 

 

    @Test

 

    void testPoll() throws InterruptedException {

 

        // 初始化隊列

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列爲空時,返回 null

 

        String resultEmpty = queue.poll();

 

        assertNull(resultEmpty);

 

 

 

        // 測試隊列不爲空時,返回隊首值並移除

 

        queue.put("Java");

 

        queue.put("C");

 

        queue.put("Python");

 

        String resultNotEmpty = queue.poll();

 

        assertEquals("Java"resultNotEmpty);

 

    }

 

 

 

    @Test

 

    void testTake() throws InterruptedException {

 

        // 初始化隊列

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列不爲空時,返回隊首值並移除

 

        queue.put("Java");

 

        queue.put("C");

 

        queue.put("Python");

 

        String resultNotEmpty = queue.take();

 

        assertEquals("Java"resultNotEmpty);

 

 

 

        // 測試隊列爲空時,會阻塞等待,一直等到隊列不爲空時再返回隊首值

 

        queue.clear();

 

        String resultEmpty = queue.take(); // 阻塞等待

 

        assertNotNull(resultEmpty);

 

    }

 

 

 

    @Test

 

    void testPollTime() throws InterruptedException {

 

        // 初始化隊列

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列不爲空時,返回隊首值並移除

 

        queue.put("Java");

 

        queue.put("C");

 

        queue.put("Python");

 

        String resultNotEmpty = queue.poll(5, TimeUnit.SECONDS);

 

        assertEquals("Java"resultNotEmpty);

 

 

 

        // 測試隊列爲空時,會阻塞等待,如果在指定時間內隊列還爲空則返回 null

 

        queue.clear();

 

        String resultEmpty = queue.poll(5, TimeUnit.SECONDS); // 等待5

 

        assertNull(resultEmpty);

 

    }

 

 

 

    @Test

 

    void testRemove() throws InterruptedException {

 

        // 初始化隊列

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列爲空時,拋出異常

 

        Throwable excpetion = assertThrows(NoSuchElementException.class, () -> {

 

            queue.remove();// 拋異常

 

        });

 

 

 

        assertEquals(nullexcpetion.getMessage());

 

 

 

        // 測試隊列不爲空時,返回隊首值並移除

 

        queue.put("Java");

 

        queue.put("C");

 

        queue.put("Python");

 

        String resultNotEmpty = queue.remove();

 

        assertEquals("Java"resultNotEmpty);

 

    }

 

 

 

@Test

 

    void testPeek() throws InterruptedException {

 

        // 初始化隊列

 

        Queue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列不爲空時,返回隊首值並但不移除

 

        queue.add("Java");

 

        queue.add("C");

 

        queue.add("Python");

 

        String resultNotEmpty = queue.peek();

 

        assertEquals("Java"resultNotEmpty);

 

        resultNotEmpty = queue.peek();

 

        assertEquals("Java"resultNotEmpty);

 

        resultNotEmpty = queue.peek();

 

        assertEquals("Java"resultNotEmpty);

 

 

 

        // 測試隊列爲空時,返回null

 

        queue.clear();

 

        String resultEmpty = queue.peek();

 

        assertNull(resultEmpty);

 

    }

 

 

 

    @Test

 

    void testElement() throws InterruptedException {

 

        // 初始化隊列

 

        Queue<String> queue = new ArrayBlockingQueue<String>(3);

 

 

 

        // 測試隊列不爲空時,返回隊首值並但不移除

 

        queue.add("Java");

 

        queue.add("C");

 

        queue.add("Python");

 

        String resultNotEmpty = queue.element();

 

        assertEquals("Java"resultNotEmpty);

 

        resultNotEmpty = queue.element();

 

        assertEquals("Java"resultNotEmpty);

 

        resultNotEmpty = queue.element();

 

        assertEquals("Java"resultNotEmpty);

 

 

 

        // 測試隊列爲空時,拋出異常

 

        queue.clear();

 

        Throwable excpetion = assertThrows(NoSuchElementException.class, () -> {

 

            queue.element();// 拋異常

 

        });

 

 

 

        assertEquals(nullexcpetion.getMessage());

 

    }

 

}

 

 

 

5.   ArrayBlockingQueue的應用案例

 

以下是一個生產者-消費者的示例。該示例模擬了1個生產者,2個消費者。當隊列滿時,則會阻塞生產者生產;當隊列空時,則會阻塞消費者消費。

 

 

 

package com.waylau.java.demo.datastructure;

 

 

 

import java.util.concurrent.ArrayBlockingQueue;

 

import java.util.concurrent.BlockingQueue;

 

 

 

/**

 

 * ArrayBlockingQueue Demo

 

 *

 

 * @since 1.0.0 202053

 

 * @author <a href="https://waylau.com">Way Lau</a>

 

 */

 

public class ArrayBlockingQueueDemo {

 

 

 

    public static void main(String[] args) {

 

        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);

 

       

 

        // 1個生產者

 

        Producer p = new Producer(queue);

 

       

 

        // 2個消費者

 

        Consumer c1 = new Consumer("c1"queue);

 

        Consumer c2 = new Consumer("c2"queue);

 

       

 

        // 啓動線程

 

        new Thread(p).start();

 

        new Thread(c1).start();

 

        new Thread(c2).start();

 

    }

 

}

 

 

 

class Producer implements Runnable {

 

    private final BlockingQueue<String> queue;

 

 

 

    Producer(BlockingQueue<String> queue) {

 

        this.queue = queue;

 

    }

 

 

 

    public void run() {

 

        try {

 

            while (true) {

 

                // 模擬耗時操作

 

                Thread.sleep(1000L);

 

 

 

                queue.put(produce());

 

            }

 

        } catch (InterruptedException ex) {

 

            ex.printStackTrace();

 

        }

 

    }

 

 

 

    String produce() {

 

        String apple = "apple: " + System.currentTimeMillis();

 

        System.out.println("produce " + apple);

 

        return apple;

 

    }

 

}

 

 

 

class Consumer implements Runnable {

 

    private final BlockingQueue<String> queue;

 

 

 

    private final String name;

 

 

 

    Consumer(String name, BlockingQueue<String> queue) {

 

        this.queue = queue;

 

        this.name = name;

 

    }

 

 

 

    public void run() {

 

        try {

 

            while (true) {

 

                // 模擬耗時操作

 

                Thread.sleep(2000L);

 

 

 

                consume(queue.take());

 

            }

 

        } catch (InterruptedException ex) {

 

            ex.printStackTrace();

 

        }

 

    }

 

 

 

    void consume(Object x) {

 

        System.out.println(this.name + " consume " + x);

 

    }

 

}

 

 

 

 

 

 

 

 

 

運行上述程序,輸出內容如下:

 

 

 

produce apple: 1590308383034

 

c2 consume apple: 1590308383034

 

produce apple: 1590308384034

 

c1 consume apple: 1590308384034

 

produce apple: 1590308385036

 

c2 consume apple: 1590308385036

 

produce apple: 1590308386036

 

c1 consume apple: 1590308386036

 

produce apple: 1590308387036

 

c2 consume apple: 1590308387036

 

produce apple: 1590308388036

 

c1 consume apple: 1590308388036

 

produce apple: 1590308389041

 

c2 consume apple: 1590308389041

 

produce apple: 1590308390041

 

c1 consume apple: 1590308390041

 

produce apple: 1590308391042

 

c2 consume apple: 1590308391042

 

produce apple: 1590308392042

 

c1 consume apple: 1590308392042

 

6.   參考引用

 

本系列歸檔至《Java數據結構及算法實戰》:https://github.com/waylau/java-data-structures-and-algorithms-in-action
《數據結構和算法基礎(Java語言實現)》(柳偉衛著,北京大學出版社出版):https://item.jd.com/13014179.html

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