Java併發編程知識點總結(十七)——LinkedBlockingQueue源碼淺析

(一)、概述

上一篇文章中我們介紹了ArrayBlockingQueue的具體實現,這一篇我們將介紹一個差不多的阻塞隊列,不過是使用鏈表實現的LinkedBlockingQueue。LinkedBlockingQueue是使用兩把ReentrantLock實現的,分別是takeLock(用於保證取出數據安全)、putLock(用於保證插入數據安全)我們通過上一篇瞭解到ArrayBlockingQueue是用一把鎖實現的,而LinkedBlockingQueue是用兩把鎖實現的。所以相比之下Linked會擁有更好的併發量。

(二)、成員變量

	//阻塞隊列的容量
    private final int capacity;

    //使用AtomicInteger來保證原子性,count用來記錄實際有多少個數據
    private final AtomicInteger count = new AtomicInteger();

  	//節點類
    transient Node<E> head;

	//尾節點
    private transient Node<E> last;

    //獲取數據的鎖
    private final ReentrantLock takeLock = new ReentrantLock();

    //獲取數據的等待隊列notEmpty,如果阻塞隊列爲空時,獲取數據的線程會進入
    private final Condition notEmpty = takeLock.newCondition();

    //插入數據的鎖
    private final ReentrantLock putLock = new ReentrantLock();

    //插入數據的等待隊列notFull,如果阻塞隊列已經滿了的時候,插入數據的線程會進入
    private final Condition notFull = putLock.newCondition();

上面需要注意的就是LInkedBlockingQueue會持有兩把鎖,分別是takeLock和putLock,保證了更好的併發量。

(三)、put方法

下面我們來看一下生產者線程中的一個重要方法:put()方法。主要用於生產者線程進行插入數據。

public void put(E e) throws InterruptedException {
		//判斷要插入的元素是否爲null
        if (e == null) throw new NullPointerException();
        int c = -1;
        //新建一個節點
        Node<E> node = new Node<E>(e);
        //獲得putLock
        final ReentrantLock putLock = this.putLock;
        //獲得插入前數據的總數
        final AtomicInteger count = this.count;
        //嘗試獲得putLock(可響應中斷)
        putLock.lockInterruptibly();
        try {
			//如果阻塞隊列已經達到最大容量
            while (count.get() == capacity) {
            	//進行等待操作
                notFull.await();
            }
            //入隊操作
            enqueue(node);
            //總數+1,返回值c是插入之前的總數
            c = count.getAndIncrement();
            //如果插入了這個數據,隊列還沒滿,就繼續從notFull隊列中喚醒線程進行插入數據
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
        	//釋放鎖
            putLock.unlock();
        }
        //如果插入之前個數爲0,就喚醒消費者的線程
        if (c == 0)
            signalNotEmpty();
    }

上面是put()方法的源碼,具體實現應該不難,這裏就不進行過多解釋,看上面的註釋應該很清晰。
可以看到put()方法調用了enqueue()方法進行入隊操作。

 private void enqueue(Node<E> node) {
       	//添加一個節點
        last = last.next = node;
    }

enqueue方法也並不是很難理解,只是簡單的進行入隊操作,所以也不過多解釋了。

(四)、take方法

下面是消費者獲取數據的重要方法:take()方法。主要用在需要獲取數據的線程進行數據的獲取

public E take() throws InterruptedException {
        E x;
        int c = -1;
        //阻塞隊列中數據的實際個數
        final AtomicInteger count = this.count;
        //獲得takeLock
        final ReentrantLock takeLock = this.takeLock;
        //嘗試獲取鎖(可響應中斷)
        takeLock.lockInterruptibly();
        try {
        	//如果數據的個數爲0
            while (count.get() == 0) {
                //進行等待
                notEmpty.await();
            }
            //出隊操作
            x = dequeue();
            //總數-1,返回值是出隊之前的總數
            c = count.getAndDecrement();
            //如果還有數據,就繼續喚醒消費者線程,取出數據
            if (c > 1)
                notEmpty.signal();
        } finally {
        	//釋放鎖
            takeLock.unlock();
        }
        //如果取數據之前,隊列是滿的,那麼現在有位置了,喚醒生產者的線程
        if (c == capacity)
            signalNotFull();
        return x;
    }

take方法的邏輯也不難,這裏也不解釋太多,我們直接來看dequeue()方法:

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;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章