JAVA多線程之——LinkedBlockingQueue

LinkedBlockingQueue

LinkedBlockingQueue隊列跟ArrayBlockingQueue一樣都實現了BlockingQueue。因此同樣是阻塞隊列,有三種刪除和三種添加的方法。LinkedBlockingQueue的底層是基於鏈表實現。ArrayBlockingQueue通過一個鎖和鎖的兩個條件對象保證併發的安全性,LinkedBlockingQueue通過兩個鎖和每個鎖的一個條件隊列來控制併發的安全性。
基本屬性如下:

static class Node<E> {
   E item;
   Node<E> next;

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

private final int capacity;

private final AtomicInteger count = new AtomicInteger(0);
private transient Node<E> head;
private transient Node<E> last;
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

ArrayBlockingQueue只有一個鎖,因此不支持添加和刪除元素並行執行,LinkedBlockingQueue有一個取鎖和放鎖,因此支持並行的取和放。但是,取和取,放和放之間是互斥的。每次只能有一個線程拿到鎖。
add方法實現
同理add方法是父類AbstractQueue隊列實現,其中調用了offer方法。如果因爲隊列滿了,添加失敗拋出IllegalStateException異常。
offer實現

public boolean offer(E e) {
   if (e == null) throw new NullPointerException();//插入元素爲空拋出異常
   final AtomicInteger count = this.count;
   if (count.get() == capacity)//隊列已滿,返回false
       return false;
   int c = -1;
   Node<E> node = new Node(e);  //把需要插入的對象封裝成隊列的一個節點
   final ReentrantLock putLock = this.putLock;
   putLock.lock();         //獲取放鎖
   try {
       if (count.get() < capacity) {
           enqueue(node);       //隊列沒滿,把節點加入隊列
           c = count.getAndIncrement();
           if (c + 1 < capacity)     //添加之後,隊列還沒滿,就通知其它等待添加線程
               notFull.signal();
       }
   } finally {
       putLock.unlock(); //釋放鎖
   }
   if (c == 0)     //如果先前隊列爲空(開始線程爲空,那麼可能所有取線程在調用take方法時候,會堵塞起來),通知被堵塞的取線程
       signalNotEmpty();
   return c >= 0;
}

enqueue方法

private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    last = last.next = node;
}

put方法

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();//插入對象爲空拋出異常
    int c = -1;
    Node<E> node = new Node(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();//獲取放鎖,如果線程被中斷拋出異常
    try {
        while (count.get() == capacity) { //如果隊列滿了,阻塞當前線程,知道隊列可以添加元素爲止
            notFull.await();
        }
        enqueue(node);  //隊列沒滿,就插入隊列尾部
        c = count.getAndIncrement();
        if (c + 1 < capacity) //插入元素之後,隊列還沒滿就通知其它放線程
            notFull.signal();
    } finally {
        putLock.unlock(); //釋放鎖
    }
    if (c == 0) //插入之前隊列爲空,就通知阻塞的取線程
        signalNotEmpty();
}

poll方法

public E poll() {
    final AtomicInteger count = this.count;
    if (count.get() == 0)  //如果隊列爲空,返回null
        return null;
    E x = null;
    int c = -1;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();   //隊列不爲空,獲取取鎖
    try {
        if (count.get() > 0) {   //再次判斷隊列是否有元素
            x = dequeue();     //獲取頭節點的值,並且刪除
            c = count.getAndDecrement();
            if (c > 1)  //隊列如果還有元素,通知其它取線程
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock(); //釋放鎖
    }
    if (c == capacity)  //這裏看上去c == capacity看上去是隊列滿了,就通知放線程,但是這裏的c返回的是count取元素之前的值。之後,這個count減1.所以這個時候count的值可能是在一直變化的。
        signalNotFull();
    return x;
}

take方法

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(); //元素個數減1,並返回原來的個數
        if (c > 1) //如果隊列沒不爲空,通知其它取線程
            notEmpty.signal();
    } finally {
        takeLock.unlock();//釋放鎖
    }
    if (c == capacity)//還清其它放線程。
        signalNotFull();
    return x;
}

remove方法

public boolean remove(Object o) {
    if (o == null) return false;//對象爲空,返回false
    fullyLock();//刪除元素,此元素的位置任意,所以取鎖和放鎖都鎖定
    try {
        for (Node<E> trail = head, p = trail.next;  //遍歷整個鏈表
             p != null;
             trail = p, p = p.next) {
            if (o.equals(p.item)) {  //判斷是否找到元素
                unlink(p, trail);  //找到元素,調用unlink方法
                return true;  //刪除成功返回true
            }
        }
        return false;
    } finally {
        fullyUnlock();//釋放鎖
    }
}

unlink

void unlink(Node<E> p, Node<E> trail) {
    p.item = null;  //將找到的元素置空
    trail.next = p.next;//把刪除元素的上一個節點和它下一個節點鏈接(等於把自己從鏈表中斷開出來)
    if (last == p)//如果元素是最後一個,就把last節點指向它的上一個節點
        last = trail;
    if (count.getAndDecrement() == capacity)//喚醒放節點
        notFull.signal();
}

LinkedBlockingQueue隊列還有一個獲取頭部的方法,但那是該方法並不移除元素。這就是peek方法

public E peek() {
    if (count.get() == 0)//隊列爲空,返回null
        return null;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();//獲取鎖
    try {
        Node<E> first = head.next;
        if (first == null) //頭爲空返回null
            return null;
        else
            return first.item;//不爲空返回其值
    } finally {
        takeLock.unlock();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章