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

(一)、概述

上一篇文章中,我們介紹了阻塞隊列的接口BlockingQeue,這一篇我們來介紹一下ArrayBlockingQueue的具體實現。
阻塞隊列通常用於生產者——消費者的模式中,當阻塞隊列爲空時,獲取數據的線程會被阻塞,當阻塞隊列滿的時候,插入數據的線程也會被阻塞。
下面我們來看一下ArrayBlockingQueue的具體實現。

(二)、成員變量

	 //用來存放數據的數組
    final Object[] items;

    //如果下次要獲取數據,獲取數據的位置
    int takeIndex;

    //如果下次要插入數據,插入數據的位置
    int putIndex;

    //數據的總數
    int count;

    //保證線程安全的鎖
    final ReentrantLock lock;

    //Condition等待隊列,主要用於獲取數據的線程
    private final Condition notEmpty;

    //Condition等待隊列,主要用於插入數據的線程
    private final Condition notFull;

上面是ArrayBlockingQueue的一些重要的成員變量,下面我們來對這些成員變量進行一些簡要的分析。

  1. Object數組類型的變量items:從這裏我們可以知道ArrayBlockingQueue是基於數組進行實現的。
  2. 獲取數據的位置takeIndex:用來標記下一次獲取數據的時候的位置
  3. 插入數據的位置putIndex:用來標記下一次插入數據的時候的位置
  4. 數組中所有數據的總數count
  5. ReentrantLock類型的變量lock:用來保證線程的安全性
  6. Condition類型的等待隊列notEmpty: 這個等待隊列主要是用來存放需要獲取數據的線程。如果數組items爲空時,就需要進入notEmpty隊列進行等待,直到生產者線程插入數據。
  7. Condition類型的等待隊列notFull: 這個等待隊列主要是用來存放需要插入的線程。如果數組items已經滿了,就需要進入notFull隊列進行等待,直到消費者取出一些數據後,才能進行插入。

(三)、put方法

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

public void put(E e) throws InterruptedException {
		//判斷數組是否爲null
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        //嘗試獲取鎖(可響應中斷)
        lock.lockInterruptibly();
        try {
        	//判斷總數count是否等於數組的長度,如果等於,表示數組滿了
            while (count == items.length)
            	//當前線程進入到notFull等待隊列中
                notFull.await();
            //當阻塞隊列不滿的時候,進行入隊操作
            enqueue(e);
        } finally {
        	//釋放鎖
            lock.unlock();
        }
    }

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

private void enqueue(E x) {
        //獲得數組
        final Object[] items = this.items;
        //將元素x存放在數組的putIndex位置上
        items[putIndex] = x;
        //如果插入的位置已經是數組中的最後一個位置了,那麼就將putIndex置爲0
        //因爲已經到達最後一個了,那就只能從第一個位置插入元素
        if (++putIndex == items.length)
            putIndex = 0;
       //總數+1
        count++;
        //喚醒notEmpty隊列中等待獲取數據的線程
        notEmpty.signal();
    }

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

(四)、take方法

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

 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //嘗試獲得鎖(可響應中斷)
        lock.lockInterruptibly();
        try {
        	//如果隊列爲空
            while (count == 0)
            	//進入到notEmpty隊列進行等待
                notEmpty.await();
            //返回獲取的元素
            return dequeue();
        } finally {
        	//釋放鎖
            lock.unlock();
        }
    }

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

private E dequeue() {
        //獲取數組
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        //獲得takeIndex位置上的數據
        E x = (E) items[takeIndex];
        //將takeIndex位置置爲null
        items[takeIndex] = null;
        //判斷是否是獲取最後一個位置的數據,如果是,下一次就需要從0開始獲取
        if (++takeIndex == items.length)
            takeIndex = 0;
        //總數減一
        count--;
        //這個是itrs類實現的迭代器,是ArrayBlcokingQueue的一個內部類
        if (itrs != null)
            itrs.elementDequeued();
        //因爲取出了一個元素,所以隊列中肯定要空位置,於是喚醒需要插入數據的線程。
        notFull.signal();
        return x;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章