读ArrayBlockingQueue源码记录

简介

ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。

ArrayBlockingQueue是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。

探索ArrayBlockingQueue

1. 主要属性


//存储队列元素的数组,是个循环数组

final Object[] items;

//拿数据索引

int takeIndex;

//放数据索引

int putIndex;

//元素个数

int count;

//重入锁

final ReentrantLock lock;

//条件对象

private final Condition notEmpty;

private final Condition notFull;

从属性可以看到,ArrayBlockingQueue的内部采用数组进行存储,采用重入锁(ReentrantLock lock)保证线程安全,利用条件对象Condition实现可阻塞式的出入队列。

2. 初始化


public ArrayBlockingQueue(int capacity) {

    this(capacity, false);

}

public ArrayBlockingQueue(int capacity, boolean fair) {

    if (capacity <= 0)

        throw new IllegalArgumentException();

    this.items = new Object[capacity];//初始化数组长度

    lock = new ReentrantLock(fair); //创建重入锁

    notEmpty = lock.newCondition(); //由lock创建条件对象

    notFull =  lock.newCondition();

}

构造函数还是很容易理解的,初始化数组,创建重入锁。其中fair是“可重入的独占锁(ReentrantLock)”的类型。fair为true,表示是公平锁;fair为false,表示是非公平锁。Condition是为了更加精细的对锁进行控制,它依赖于Lock,通过某个条件对多线程进行控制。

3.插入

ArrayBlockingQueue的插入方法有add、offer、put.

add()方法,可以从源码看到实际调用的是offer方法。


public boolean add(E e) {

    return super.add(e); //调用父类的add()

}

//父类的方法

public boolean add(E e) {

    if (offer(e))

        return true;

    else

        throw new IllegalStateException("Queue full");

}

再看offer方法:作用是将元素e插入到阻塞队列的尾部,如果队满,返回false,即插入失败。否则,插入元素,这里调用的是私有方法insert();


public boolean offer(E e) {

    // 创建插入的元素是否为null,是的话抛出NullPointerException异常

    checkNotNull(e);

    // 加锁,保证线程安全

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        //队列满,返回false

        if (count == items.length)

            return false;

        else {

            //插入方法

            insert(e);

            return true;

        }

    } finally {

        lock.unlock();

    }

}

接着看insert()源码,在插入元素后唤醒notEmpty的等待线程


private void insert(E x) {

    //将x放入数组中

    items[putIndex] = x;

    //设置下一个放入索引

    putIndex = inc(putIndex);

    //元素数量+1

    ++count;

    //唤醒notEmpty的等待线程

    notEmpty.signal();

}

inc()方法:若i+1的值等于“队列的长度”,即添加元素之后,队列满;则设置“下一个被添加元素的索引”为0。


final int inc(int i) {

    return (++i == items.length) ? 0 : i;

}

offer()还有另一个重载方法.这个重载方法多了两个参数,实现的是超时退出;如果队列为满,会使得当前线程进入等待状态等待一定的时长,等待期间如果队列不为满了就会被唤醒,然后元素添加成功,如果超过了参数设置的时限队列仍为满,元素就会添加失败,返回false。


public boolean offer(E e, long timeout, TimeUnit unit)

    throws InterruptedException {

    checkNotNull(e);

    long nanos = unit.toNanos(timeout);

    final ReentrantLock lock = this.lock;

    lock.lockInterruptibly(); //允许线程中断

    try {

        while (count == items.length) {

            if (nanos <= 0)

                return false;

            nanos = notFull.awaitNanos(nanos);//等待并且设置最大等待时间

        }

        insert(e);

        return true;

    } finally {

        lock.unlock();

    }

}

put()方法:与可延迟的offer()相似,唯一不同的是没有设置超时等待时间,即当队列为空时,线程一直等待下去。


public void put(E e) throws InterruptedException {

    checkNotNull(e);

    final ReentrantLock lock = this.lock;

    lock.lockInterruptibly();

    try {

        while (count == items.length)

            notFull.await();

        insert(e);

    } finally {

        lock.unlock();

    }

}

4.取出

ArrayBlockingQueue有三个取出元素的方法,分别为poll(),take().

poll()方法:可以看到poll()方法实际调用extract()方法。


public E poll() {

    // 加锁,保证线程安全

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        // 队列为空返回null,否则调用extract方法

        return (count == 0) ? null : extract();

    } finally {

        lock.unlock();

    }

}

再看extract()方法:方法也很简单,获取取位置上的元素后将其位置的设置为空,并唤醒notFull上的等待线程


private E extract() {

    final Object[] items = this.items;

    //获取取索引上的元素,强制将元素转换为“泛型E”

    E x = this.<E>cast(items[takeIndex]);

    //将取索引上的位置设置为null,即删除

    items[takeIndex] = null;

    //设置下一个取的位置

    takeIndex = inc(takeIndex);

    //队列长度减一

    --count;

    // 唤醒notFull上的等待线程。

    notFull.signal();

    return x;

}

同时,poll()也有一个重载方法poll(long timeout, TimeUnit unit) ,该方法指定等待时间,如果队列无元素,会使得当前线程进入等待状态等待一定的时长,等待期间如果队列数量不为空了就会被唤醒,获取元素成功,如果超过了参数设置的时限队列仍为空,返回false。


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();

    }

}

take()方法与等待poll()相似,唯一不同就是没有设置超时时间,即如果队列为空,会无限等待,除非线程被中断。


public E take() throws InterruptedException {

    final ReentrantLock lock = this.lock;

    lock.lockInterruptibly();

    try {

        while (count == 0)

            notEmpty.await();

        return extract();

    } finally {

        lock.unlock();

    }

}

5.其他方法

ArrayBlockingQueue还提供其他方法,例如size()获取当前队列长度,该方法与ConcurrentHashMap的size()不同,ArrayBlockingQueue的size()方法不需要进行多次对比判断是否改变,所以性能比ConCurrentHashMap的size()效率高.


public int size() {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        return count;

    } finally {

        lock.unlock();

    }

}

clear()方法清空队列,remove(object)移除指定元素等,这里不再进行延伸。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容