LinkedBlockingQueue源碼分析

在很久前我們分析了ArrayBlockingQueue,今天我們來分析分析LinkedBlockingQueue.
看看他們之間有什麼區別,在什麼時候我們用什麼那個阻塞隊列。
我們先來看看LinkedBlockingQueue的一些屬性。

定義了一個內部類,相當於一個結點,用來對元素進行操作的結構。
static class Node<E> {
        E item;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

LinkedBlockingQueue的屬性

    隊列的大小,可以容納元素的數量
    private final int capacity;

    當前的隊列中的元素的個數,用的是原子鎖操作,保證內存的可見性,採用的是cas算法,作爲數據的保證,這個我也不懂,下次我們看源碼一起分析。
    private final AtomicInteger count = new AtomicInteger();
    頭結點
    transient Node<E> head;
    尾結點
    private transient Node<E> last;
    取元素的單獨一把鎖,這裏就跟ArrayBlockingQueue有區別了,ArrayBlockingQueue是添加和取元素都是同一把鎖,不能並行的操作。
    private final ReentrantLock takeLock = new ReentrantLock();
    隊列不爲空的信號
    private final Condition notEmpty = takeLock.newCondition();
    存放元素的單獨一把鎖
    private final ReentrantLock putLock = new ReentrantLock();
   隊列沒有滿的信號
    private final Condition notFull = putLock.newCondition();

LinkedBlockingQueue的構造方法

調用第二個構造方法
public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    傳入有個指定大小的容量,進行初始化
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

    傳入一個集合,對集合中的元素進行入隊的操作
      public LinkedBlockingQueue(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // Never contended, but necessary for visibility
        try {
            int n = 0;
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                if (n == capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }

從構造方法我們可以看到,有個和ArrayBlockingQueue隊列的區別。在進行初始化的時候LinkedBlockingQueue可以不必指定初始容量的大小。默認的大小爲Interge的最大值。ArrayBlockingQueue在進行初始化的時候必須指定一個大小。

我們簡單分析幾個常用的方法。
put(E e)

public void put(E e) throws InterruptedException {
        if (e == null) 
        throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
                notFull.await();
            }
            看過上一篇博客的同學就知道,其實這都是一樣的,沒有什麼多大的區別,我們講講這個enqueue就行了。
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }
 private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        last = last.next = node;
        加進去一個元素以後,把新加進去的元素設置爲最後一個元素。
    }

take(E e)

這個也沒什麼講的,前面我們已經將過了。我們分析分析dequeue方法
 public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
     private E dequeue() {
    取出一個元素
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        從新獲得頭結點
        head = first;
        獲得需要取得元素的值
        E x = first.item;
        first.item = null;
        return x;
    }

這裏大家注意點,所有的釋放鎖的操作都要在finally {}裏面進行,操作,防止異常的情況不能正常的釋放鎖。

我們總結一下,這兩個阻塞隊列有什麼區別。
1. 隊列中鎖的實現不同

ArrayBlockingQueue實現的隊列中的鎖是沒有分離的,即生產和消費用的是同一個鎖;

LinkedBlockingQueue實現的隊列中的鎖是分離的,即生產用的是putLock,消費是takeLock,這就意味着在大併發的時候我們可以使用這個隊列進行操作,提高效率。
  1. 在生產或消費時操作不同

    ArrayBlockingQueue實現的隊列中在生產和消費的時候,是直接將枚舉對象插入或移除的;

    LinkedBlockingQueue實現的隊列中在生產和消費的時候,需要把枚舉對象轉換爲Node進行插入或移除,會影響性能

  2. 隊列大小初始化方式不同

    ArrayBlockingQueue實現的隊列中必須指定隊列的大小;

    LinkedBlockingQueue實現的隊列中可以不指定隊列的大小,但是默認是Integer.MAX_VALUE

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