Java集合--阻塞隊(duì)列(ArrayBlockingQueue)

1 ArrayBlockingQueue

ArrayBlockingQueue是一個(gè)阻塞隊(duì)列佑女,底層使用數(shù)組結(jié)構(gòu)實(shí)現(xiàn),按照先進(jìn)先出(FIFO)的原則對(duì)元素進(jìn)行排序痴柔。

ArrayBlockingQueue是一個(gè)線程安全的集合裳涛,通過(guò)ReentrantLock鎖來(lái)實(shí)現(xiàn),在并發(fā)情況下可以保證數(shù)據(jù)的一致性迁筛。

此外煤蚌,ArrayBlockingQueue的容量是有限的,數(shù)組的大小在初始化時(shí)就固定了细卧,不會(huì)隨著隊(duì)列元素的增加而出現(xiàn)擴(kuò)容的情況尉桩,也就是說(shuō)ArrayBlockingQueue是一個(gè)“有界緩存區(qū)”。

在下面圖片中贪庙,以數(shù)組形式展示了一個(gè)ArrayBlockingQueue:

image

當(dāng)向隊(duì)列插入元素時(shí)蜘犁,首先會(huì)插入到數(shù)組的0角標(biāo)處,再有新元素進(jìn)來(lái)時(shí)止邮,依次類推这橙,角標(biāo)1奏窑、角標(biāo)2、角標(biāo)3析恋。

整個(gè)item[]就是一個(gè)隊(duì)伍良哲,我們用時(shí)間來(lái)排序,展示入隊(duì)場(chǎng)景助隧。

image

而當(dāng)有元素出隊(duì)時(shí)筑凫,先移除角標(biāo)為0的元素,與入隊(duì)一樣并村,依次類推巍实,移除角標(biāo)1、角標(biāo)2...上的元素哩牍。

這也形成了“先進(jìn)先出”棚潦。

接下來(lái),我們來(lái)看看ArrayBlockingQueue的源碼實(shí)現(xiàn)膝昆!

  • 構(gòu)造方法

在多線程中丸边,默認(rèn)不保證線程公平的訪問(wèn)隊(duì)列。

什么叫做公平訪問(wèn)隊(duì)列荚孵?我們都知道妹窖,在ArrayBlockingQueue中為了保證數(shù)據(jù)的安全,使用了ReentrantLock鎖收叶。由于鎖的引入骄呼,導(dǎo)致了線程之間的競(jìng)爭(zhēng)。當(dāng)有一個(gè)線程獲取到鎖時(shí)判没,其余線程處于等待狀態(tài)蜓萄。當(dāng)鎖被釋放時(shí),所有等待線程為奪鎖而競(jìng)爭(zhēng)澄峰。

而所謂的公平訪問(wèn)嫉沽,就是等待的線程在獲取鎖而競(jìng)爭(zhēng)時(shí),按照等待的先后順序進(jìn)行獲取操作俏竞,先等待的先獲取耻蛇,后等待的后獲取。

而非公平訪問(wèn)胞此,就是在獲取時(shí)候,無(wú)論是先等待還是后等待的線程跃捣,均有可能獲取到鎖漱牵。

在ArrayBlockingQueue中,由于公平鎖會(huì)降低隊(duì)列的性能疚漆,因而使用非公平鎖(默認(rèn))酣胀。

是否公平刁赦,根據(jù)ReentrantLock對(duì)象來(lái)實(shí)現(xiàn)---ReentrantLock lock = new ReentrantLock(false),具體看下構(gòu)造便可得知闻镶。

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    //隊(duì)列實(shí)現(xiàn):數(shù)組
    final Object[] items;

    //當(dāng)讀取元素時(shí)數(shù)組的下標(biāo)(下一個(gè)被添加元素的索引)
    int takeIndex;

    //添加元素時(shí)數(shù)組的下標(biāo) (下一個(gè)被取出元素的索引)
    int putIndex;

    //隊(duì)列中元素個(gè)數(shù):
    int count;

    //鎖:
    final ReentrantLock lock;

    //控制take()操作時(shí)是否讓線程等待
    private final Condition notEmpty;

    //控制put()操作時(shí)是否讓線程等待
    private final Condition notFull;

    //初始化隊(duì)列容量構(gòu)造:
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    //帶初始容量大小和公平鎖隊(duì)列(公平鎖通過(guò)ReentrantLock實(shí)現(xiàn)):
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
}
  • 插入元素

在ArrayBlockingQueue中甚脉,提供了兩種不同形式的元素插入--阻塞式和非阻塞式。

對(duì)于阻塞式插入來(lái)說(shuō)铆农,當(dāng)隊(duì)列中的元素已滿時(shí)牺氨,則會(huì)將此線程停止,讓其處于等待狀態(tài)墩剖,直到隊(duì)列中有空余位置產(chǎn)生猴凹。

//向隊(duì)列尾部添加元素,如果隊(duì)列滿了岭皂,則線程等待
public void put(E e) throws InterruptedException {
    //不能插入非空元素,會(huì)拋出異常
    checkNotNull(e);
    //上鎖郊霎,保證數(shù)據(jù)安全
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        //隊(duì)列中元素 == 數(shù)組長(zhǎng)度(隊(duì)列滿了),則線程等待
        while (count == items.length)
            notFull.await();
        //添加隊(duì)列元素
        insert(e);
    } finally {
        //插入完成,釋放鎖
        lock.unlock();
    }
}

而對(duì)于非阻塞式來(lái)說(shuō)爷绘,當(dāng)隊(duì)列中的元素已滿時(shí)书劝,并不會(huì)阻塞此線程的操作,而是讓其返回又或者是拋出異常土至。

//向隊(duì)列尾部添加元素购对,隊(duì)列滿了返回false
public boolean offer(E e) {
    //不能插入非空元素,會(huì)拋出異常
    checkNotNull(e);
    //上鎖,保證數(shù)據(jù)安全
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //隊(duì)列中元素 == 數(shù)組長(zhǎng)度(隊(duì)列滿了),則返回false
        if (count == items.length)
            return false;
        else {
            //添加隊(duì)列元素
            insert(e);
            return true;
        }
    } finally {
        //插入完成毙籽,釋放鎖
        lock.unlock();
    }
}

上面的offer(E e)并不會(huì)阻塞線程的執(zhí)行洞斯,但是如果想讓阻塞和非阻塞相結(jié)合的話,需要怎么處理?

ArrayBlockingQueue為我們提供了折中的方法--offer(E e, long timeout, TimeUnit unit);

向隊(duì)列尾部添加元素坑赡,可以設(shè)置線程等待時(shí)間烙如,如果超過(guò)指定時(shí)間隊(duì)列還是滿的,則返回false毅否;

public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
    //不能插入非空元素,會(huì)拋出異常
    checkNotNull(e);
    //轉(zhuǎn)換成超時(shí)時(shí)間閥值:
    long nanos = unit.toNanos(timeout);
    //上鎖亚铁,保證數(shù)據(jù)安全
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        //對(duì)隊(duì)列是否元素滿了,做判斷螟加。
        while (count == items.length) {
            //如果隊(duì)列是滿的徘溢,則每次遍歷都去遞減一次nanos的值
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        //添加隊(duì)列元素
        insert(e);
        return true;
    } finally {
        //插入完成,釋放鎖
        lock.unlock();
    }
}

以上添加方法捆探,都是通過(guò)返回false/true來(lái)實(shí)現(xiàn)的然爆,而在ArrayBlockingQueue中,還提供了集合最原始的插入方法--add(E e)黍图。

該方法在插入時(shí)候曾雕,如果隊(duì)列中的元素滿了,則會(huì)拋出異常助被。如果插入成功剖张,則返回true切诀。

在add(E e)中,使用父類的add(E e),實(shí)際上其底層也是調(diào)用的offer(E e)方法搔弄。

//向隊(duì)列尾部添加元素幅虑,隊(duì)列滿了拋出異常;
public boolean add(E e) {
    return super.add(e);
}

ArrayBlockingQueue中顾犹,最底層的插入方法倒庵,上面的各種實(shí)現(xiàn),都是基于insert(E x)來(lái)實(shí)現(xiàn)的蹦渣。由于insert(E x)是用private來(lái)修飾的哄芜,所以我們不能直接對(duì)其進(jìn)行調(diào)用。

//插入元素到隊(duì)尾柬唯,調(diào)整putIndex认臊,喚起等待的獲取線程
private void insert(E x) {
    //向數(shù)組中插入元素
    items[putIndex] = x;
    //設(shè)置下一個(gè)被取出元素的索引
    putIndex = inc(putIndex);
    //增加隊(duì)列元素個(gè)數(shù):
    ++count;
    //喚醒notEmpty上的等待線程
    notEmpty.signal();
}
  • 獲取元素

//獲取隊(duì)列頭部元素,如果隊(duì)列為空锄奢,則返回null.不為空失晴。
// 則返回隊(duì)列頭部,并從隊(duì)列中刪除拘央。
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : extract();
} finally {
lock.unlock();
}
}

    //返回隊(duì)列的頭部元素涂屁,并從隊(duì)列中刪除。如果隊(duì)列為空灰伟,則等待
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果隊(duì)列為空拆又,則進(jìn)行等待
            while (count == 0)
                notEmpty.await();

            //獲取頭部元素:
            return extract();
        } finally {
            lock.unlock();
        }
    }

    //獲取隊(duì)列頭部元素,如果隊(duì)列為空栏账,則設(shè)置線程等待時(shí)間帖族,超過(guò)指定時(shí)間,還為空挡爵,則返回null竖般。
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return extract();
        } finally {
            lock.unlock();
        }
    }

以上就是關(guān)于ArrayBlockingQueue的全部?jī)?nèi)容!下面茶鹃,我們繼續(xù)說(shuō)說(shuō)LinkedBlockingQueue

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涣雕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闭翩,更是在濱河造成了極大的恐慌挣郭,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疗韵,死亡現(xiàn)場(chǎng)離奇詭異兑障,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門旺垒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肤无,你說(shuō)我怎么就攤上這事先蒋。” “怎么了宛渐?”我有些...
    開(kāi)封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵竞漾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我窥翩,道長(zhǎng)业岁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任寇蚊,我火速辦了婚禮笔时,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仗岸。我一直安慰自己允耿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布扒怖。 她就那樣靜靜地躺著较锡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盗痒。 梳的紋絲不亂的頭發(fā)上蚂蕴,一...
    開(kāi)封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音俯邓,去河邊找鬼骡楼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛看成,可吹牛的內(nèi)容都是我干的君编。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼川慌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吃嘿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起梦重,我...
    開(kāi)封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤兑燥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后琴拧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體降瞳,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挣饥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片除师。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扔枫,靈堂內(nèi)的尸體忽然破棺而出汛聚,到底是詐尸還是另有隱情,我是刑警寧澤短荐,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布倚舀,位于F島的核電站,受9級(jí)特大地震影響忍宋,放射性物質(zhì)發(fā)生泄漏痕貌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一糠排、第九天 我趴在偏房一處隱蔽的房頂上張望舵稠。 院中可真熱鬧,春花似錦乳讥、人聲如沸柱查。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)唉工。三九已至,卻和暖如春汹忠,著一層夾襖步出監(jiān)牢的瞬間淋硝,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工宽菜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谣膳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓铅乡,卻偏偏與公主長(zhǎng)得像继谚,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阵幸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348