Queue API的幾種實現詳解

Queue API的幾種方法的使用

方法名稱 作用 描述
add 添加元素到隊列 如果隊列滿了就拋異常java.lang.IllegalStateException
remove 移除並且返回隊列頭部元素 如果隊列爲null,就拋異常java.util.NoSuchElementException
element 返回隊列頭部元素,不會移除元素 如果隊列爲null,就拋異常java.util.NoSuchElementException
offer 添加元素到隊列 如果隊列滿了就返回false,不會阻塞
poll 移除並且返回隊列頭部元素 如果隊列爲null,就返回null,不會阻塞
peek 返回隊列頭部元素,不會移除元素 如果隊列爲null,就返回null,不會阻塞
put 添加元素到隊列 如果隊列滿了就阻塞
take 移除並且返回隊列頭部元素 如果隊列爲null,就阻塞

ArrayBlockingQueue原理及源碼解析

根據名字,可以知道,ArrayBlockingQueue底層是數組實現的,而且是阻塞的隊列,下面看下put元素和take元素時的圖解:
在這裏插入圖片描述
上面的圖基本上就是ArrayBlockingQueue隊列使用時的底層實現原理了,下面根據源碼來看一下。

ArrayBlockingQueue的成員變量

    /** 存放元素 */
    final Object[] items;

    /** 記錄下一次從什麼位置開始取元素或者移除元素 */
    int takeIndex;

    /** 記錄下一次從什麼位置開始放元素 */
    int putIndex;

    /** 隊列中元素的數量 */
    int count;

    /** 可重入鎖,用來放元素和取元素時加鎖 */
    final ReentrantLock lock;

    /** 取元素的等待集合 */
    private final Condition notEmpty;

    /** 存放元素的等待集合 */
    private final Condition notFull;

ArrayBlockingQueue的offer和put方法

offer方法源碼:

    /**
     * 存放一個元素到隊列
     * 如果隊列未滿,就放入隊列的尾部
     * 如果隊列已滿,就返回false
     *
     * @throws NullPointerException 如果存放的元素是null,拋異常
     */
    public boolean offer(E e) {
        //校驗放入的元素是否爲空,每次放入元素到隊列,都會校驗
        checkNotNull(e);
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //如果隊列中的元素已經滿了,就返回false
            if (count == items.length)
                return false;
            else {
                //未滿就調用方法,放入元素到隊列
                enqueue(e);
                return true;
            }
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

put方法源碼:

    /**
     * 存放一個元素到隊列
     * 如果隊列未滿,就放入隊列的尾部
     * 如果隊列已滿,就阻塞等待
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        //校驗放入的元素是否爲空,每次放入元素到隊列,都會校驗
        checkNotNull(e);
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果隊列中的元素已經滿了,就阻塞,掛起當前線程
            //這裏使用while而不使用if,是爲了防止僞喚醒
            while (count == items.length)
                notFull.await();
            //未滿就調用方法,放入元素到隊列
            enqueue(e);
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

參數校驗:

private static void checkNotNull(Object v) {
        //如果傳入的元素是null,就拋異常
        if (v == null)
            throw new NullPointerException();
    }

共同調用的enqueue方法:

    /**
     * 將指定的元素放入隊列的尾部
     * 只有持有鎖纔可以調用
     */
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        //獲取隊列中的元素
        final Object[] items = this.items;
        //putIndex就是要放入的元素在隊列中的的索引
        items[putIndex] = x;
        //如果放入元素之後,隊列滿了,就把putIndex置爲0
        //意思是下一次向隊列中放元素,就是放入隊列的第一個位置了
        //putIndex的作用就是記錄下一次元素應該放到哪裏
        if (++putIndex == items.length)
            putIndex = 0;
        //元素的個數加一
        count++;
        //喚醒拿元素沒有拿到掛起的線程,告訴它:
        //元素已經放入了隊列,可以取元素了
        notEmpty.signal();
    }

ArrayBlockingQueue的poll和take方法

poll方法源碼:

    /**
     * 從隊列中拿元素
     * 如果隊列中沒有元素了,就返回null
     * 如果隊列中有元素,就返回
     */
    public E poll() {
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //如果隊列中沒有元素,就返回Null
            //否則就調用方法取元素然後返回
            return (count == 0) ? null : dequeue();
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

take方法源碼:

    /**
     * 從隊列中拿元素
     * 如果隊列中沒有元素了,就阻塞
     * 如果隊列中有元素,就返回
     */
    public E take() throws InterruptedException {
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果隊列中沒有元素,就讓線程阻塞
            //使用while而不使用if,是爲了防止僞喚醒
            while (count == 0)
                //掛起線程
                notEmpty.await();
            //如果隊列中有元素存在,就取出返回
            return dequeue();
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

dequeue方法源碼:

    /**
     * 從隊列中取元素
     * 只有持有鎖纔可以調用
     */
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        //獲取隊列中的元素
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        //獲取要取出的是哪一個元素
        E x = (E) items[takeIndex];
        //取出後設置爲Null
        items[takeIndex] = null;
        //要是取出的是隊列中的最後一個元素
        //就把takeIndex置爲0,意思是下一次取元素從隊列第一個開始取
        if (++takeIndex == items.length)
            takeIndex = 0;
        //隊列中元素的個數減一
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        //喚醒因爲存放元素時,隊列滿了,掛起的線程
        //告訴它,可以存放元素了
        notFull.signal();
        //返回取出的元素
        return x;
    }

ArrayBlockingQueue的peek方法

/**
     * 返回隊列頭部的元素
     * 如果隊列中沒有元素了,就返回null
     * 如果隊列中有元素,就返回
     */
    public E peek() {
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //有元素就返回,沒有就返回null
            return itemAt(takeIndex); // null when queue is empty
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

注意:實例化ArrayBlockingQueue時必須指定隊列的容量大小,否則會編譯錯誤

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

LinkedBlockingDeque原理及源碼解析

根據名字,可以知道LinkedBlockingDeque,底層是使用鏈表的方式存儲元素的,而且是阻塞隊列,初始化一個LinkedBlockingDeque可以不用指定隊列的容量,即可以指定一個無界的隊列。

LinkedBlockingDeque的成員變量

  private static final long serialVersionUID = -387911632671998426L;

    /** 鏈表節點類 */
    static final class Node<E> {
        /**
         * 鏈表中的元素
         */
        E item;

        /**
         * 上一個節點
         */
        Node<E> prev;

        /**
         * 下一個節點
         */
        Node<E> next;

        //構造函數
        Node(E x) {
            item = x;
        }
    }

    /**
     * 指向第一個節點的指針
     */
    transient Node<E> first;

    /**
     * 指向最後一個節點的指針
     */
    transient Node<E> last;

    /** 隊列中元素的個數 */
    private transient int count;

    /** 隊列的容量 */
    private final int capacity;

    /** 可重入鎖 */
    final ReentrantLock lock = new ReentrantLock();

    /** 取元素的等待集合 */
    private final Condition notEmpty = lock.newCondition();

    /** 存放元素的等待集合 */
    private final Condition notFull = lock.newCondition();

LinkedBlockingDeque的構造函數

    /**
     * 無參構造函數
     * 可以創建一個無界的隊列,隊列容量是int的最大值
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingDeque() {
        this(Integer.MAX_VALUE);
    }

    /**
     * 創建一個指定邊界的隊列
     * 隊列的大小由編碼時指定
     * @param capacity 指定的隊列容量值
     * @throws IllegalArgumentException if {@code capacity} is less than 1
     */
    public LinkedBlockingDeque(int capacity) {
        //如果傳入的指定隊列容量值小於0,就拋異常
        if (capacity <= 0) throw new IllegalArgumentException();
        //指定的隊列容量大小
        this.capacity = capacity;
    }

    /**
     * 創建一個包含指定集合元素的隊列
     *
     * @param c 要包含這個元素的集合
     * @throws NullPointerException 如果指定的集合或者其中的元素是null,拋異常
     */
    public LinkedBlockingDeque(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock(); // Never contended, but necessary for visibility
        try {
            //遍歷指定的集合,然後放入隊列
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                if (!linkLast(LinkedBlockingDeque.Node<E>(e)))
                    throw new IllegalStateException("Deque full");
            }
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

LinkedBlockingDeque的offer和put方法

offer方法源碼:

    /**
     * 添加一個元素到隊列
     * 隊列未滿,就直接添加進去
     * 隊列已滿,就返回false
     * @throws NullPointerException 添加元素爲null。拋異常
     */
    public boolean offer(E e) {
        return offerLast(e);
    }
    /**
     * @throws NullPointerException {@inheritDoc}
     */
    public boolean offerLast(E e) {
        //如果添加的元素是null,就拋異常
        if (e == null) throw new NullPointerException();
        //初始化一個鏈表,把元素放入鏈表
        Node<E> node = new Node<E>(e);
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return linkLast(node);
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }
    /**
     * 把元素添加到鏈表,如果隊列元素滿了,就返回false
     */
    private boolean linkLast(Node<E> node) {
        // assert lock.isHeldByCurrentThread();
        //如果添加進去元素,隊列的長度大於隊列的容量,就返回false
        if (count >= capacity)
            return false;
        //獲取鏈表的最後一個節點
        Node<E> l = last;
        //把鏈表的最後一個節點做爲上一個節點
        node.prev = l;
        //把當前要添加的元素放入鏈表
        last = node;
        //如果鏈表的第一個節點是null,就把元素放入鏈表的第一個位置
        if (first == null)
            first = node;
        else
            //否則就把元素放入鏈表最後一個節點的下一個節點中
            l.next = node;
        //隊列中元素的個數加一
        ++count;
        //喚醒拿元素時阻塞的線程
        notEmpty.signal();
        //添加成功,返回true
        return true;
    }

put方法源碼:

    /**
     * 添加一個元素到隊列
     * 隊列未滿,就直接添加進去
     * 隊列已滿,就阻塞
     * @throws NullPointerException {@inheritDoc}
     * @throws InterruptedException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        putLast(e);
    }
   /**
     * @throws NullPointerException {@inheritDoc}
     * @throws InterruptedException {@inheritDoc}
     */
    public void putLast(E e) throws InterruptedException {
        //如果添加的元素是null,就返回NullPointerException
        if (e == null) throw new NullPointerException();
        //初始化一個鏈表,把元素放入鏈表
        Node<E> node = new Node<E>(e);
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //當元素沒有添加成功,就掛起
            //linkLast方法在上面offer中已經寫了
            while (!linkLast(node))
                notFull.await();
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

LinkedBlockingDeque的poll和take方法

poll方法源碼:

    /**
     * 從隊列中取元素
     * 隊列有元素存在,取出元素
     * 隊列爲空,返回null
     */
    public E poll() {
        return pollFirst();
    }
public E pollFirst() {
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //隊列有元素就返回,否則就返回null
            return unlinkFirst();
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

take方法源碼:

    /**
     * 從隊列中取元素
     * 隊列有元素存在,取出元素
     * 隊列爲空,就阻塞
     */
    public E take() throws InterruptedException {
        return takeFirst();
    }
 public E takeFirst() throws InterruptedException {
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E x;
            //當隊列爲空時,就阻塞
            //while防止僞喚醒
            while ( (x = unlinkFirst()) == null)
                notEmpty.await();
            //隊列有元素存在,返回取出的元素
            return x;
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

unlinkFirst方法源碼:

    /**
     * Removes and returns first element, or null if empty.
     * 刪除並返回第一個元素,如果爲空則返回null
     */
    private E unlinkFirst() {
        // assert lock.isHeldByCurrentThread();
        //獲取隊列中第一個元素
        //及鏈表的第一個節點
        Node<E> f = first;
        //第一個元素爲null,就返回null
        if (f == null)
            return null;
        //獲取第一個節點的下一個節點
        Node<E> n = f.next;
        //獲取第一個節點的元素值
        E item = f.item;
        //把值設置爲null
        f.item = null;
        f.next = f; // help GC
        //把隊列的第一個元素設置爲:
        //要移除元素的下一個節點
        first = n;
        //如果是null,就把最後一個節點設置爲null
        if (n == null)
            last = null;
        else
            //否則就把上一個節點設置爲Null
            n.prev = null;
        //隊列的元素個數減一
        --count;
        //喚醒存放元素時,掛起的線程
        notFull.signal();
        //返回取出的元素
        return item;
    }

LinkedBlockingDeque的peek方法

    /**
     * 返回隊列頭部的元素
     * 隊列有元素存在,返回元素
     * 隊列爲空,返回null
     */
    public E peek() {
        return peekFirst();
    }
public E peekFirst() {
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //如果隊列頭部元素爲空就返回null
            //否則就返回頭部元素
            return (first == null) ? null : first.item;
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

ConcurrentLinkedDeque

根據英文意思,可以知道,ConcurrentLinkedDeque是一個非阻塞的隊列,底層是鏈表實現,具體源碼請查看其他文章。

SynchronousQueue的簡單使用

SynchronousQueue是一個容量爲0的隊列,隊列內部不存儲元素;當put一個元素時,如果沒有take方法去拿元素,就會一直阻塞,直到有take方法去拿元素纔會結束;同樣的,take元素時,如果沒有put元素,那麼就會一直阻塞,直到有put元素,纔會結束,下面看下示例:

 public static void main(String[] args) {
        SynchronousQueue<String> queue = new SynchronousQueue<>();

        Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("start work...");
                //向隊列放入元素
                queue.offer("hello");
                System.out.println("end work...");
            }
        });
        th.start();
        //打印取出的元素
        System.out.println(queue.poll());
        //打印結果爲null
        try {
            //打印結果爲hello
            System.out.println(queue.take());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

從上面的例子可以看出,在SynchronousQueue隊列中,offer進去的元素可能會丟失。

SynchronousQueue<String> queue = new SynchronousQueue<>();

Thread th = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("start work...");
        //向隊列放入元素
        try {
            //會阻塞
            queue.put("hello");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end work...");
    }
});
th.start();

try {
    //打印結果爲hello
    System.out.println(queue.take());
} catch (InterruptedException e) {
    e.printStackTrace();
}

put元素時,如果沒有take方法去拿元素就會阻塞;如果這時使用poll方法去拿元素,取出的元素是null,而且不會結束阻塞。
take元素時,如果沒有put或者offer元素進隊列,也會阻塞。

PriorityQueue的簡單使用

PriorityQueue是一個優先級隊列,會自動對元素進行排序,也可以自己指定排序規則。

public static void main(String[] args) {
        PriorityQueue<String> queue = new PriorityQueue<String>();
        //入隊列
        queue.offer("36");
        queue.offer("21");
        queue.offer("57");
        queue.offer("78");
        queue.offer("22");
        //出隊列
        System.out.println(queue.poll());//21
        System.out.println(queue.poll());//22
        System.out.println(queue.poll());//36
        System.out.println(queue.poll());//57
        System.out.println(queue.poll());//78
 }

PriorityQueue還可以自定義排序規則,通過實現compare方法即可:

public static void main(String[] args) {
        PriorityQueue<Student> queue = new PriorityQueue<Student>(10, new Comparator<Student>() {
            //自定義比較規則
            @Override
            public int compare(Student o1, Student o2) {
                if (o1.age > o2.age) {
                    return 1;
                } else  {
                    return -1;
                }
            }
        });
        //入隊列
        queue.offer(new Student("小明", 15));
        queue.offer(new Student("小紅", 12));
        queue.offer(new Student("小黑", 16));
        //出隊列
        System.out.println( queue.poll().age);//12
        System.out.println(queue.poll().age);//15
        System.out.println(queue.poll().age);//16
    }

    static class Student {
        public String name;
        public int age;

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

結束語

本文只講了兩個隊列的源碼,可能存在不足或者不夠深入的地方,還希望有朋友可以多多指正,其他幾個隊列的源碼,後續有時間的話再做解析,感謝閱讀!

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