Java并发51:并发集合系列-基于独占锁+数组实现的单向阻塞有界队列ArrayBlockingQueue

转载 2018年04月15日 14:21:54

[超级链接:Java并发学习系列-绪论]
[系列序章:Java并发43:并发集合系列-序章]


原文地址:http://www.importnew.com/25566.html

一、 前言

上节介绍了无界链表方式的阻塞队列LinkedBlockingQueue,本节来研究下有界使用数组方式实现的阻塞队列ArrayBlockingQueue

二、 ArrayBlockingQueue类图结构

这里写图片描述

如图ArrayBlockingQueue内部

  • 有个数组items用来存放队列元素,
  • putindex下标标示入队元素下标,takeIndex是出队下标,count统计队列元素个数,从定义可知道并没有使用volatile修饰,这是因为访问这些变量使用都是在锁块内,并不存在可见性问题。
  • 另外有个独占锁lock用来对出入队操作加锁,这导致同时只有一个线程可以访问入队出队,
  • 另外notEmpty,notFull条件变量用来进行出入队的同步。

另外构造函数必须传入队列大小参数,所以为有界队列,默认是Lock为非公平锁。

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();
    notFull =  lock.newCondition();
}

三、offer操作

在队尾插入元素,如果队列满则返回false,否者入队返回true。

public boolean offer(E 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();
    }
}


private void insert(E x) {

    //元素入队
    items[putIndex] = x;

    //计算下一个元素应该存放的下标
    putIndex = inc(putIndex);
    ++count;
    notEmpty.signal();
}

//循环队列,计算下标
final int inc(int i) {
    return (++i == items.length) ? 0 : i;
}

这里由于在操作共享变量前加了锁,所以不存在内存不可见问题。

加过锁后获取的共享变量都是从主内存获取的,而不是在CPU缓存或者寄存器里面的值。

释放锁后修改的共享变量值会刷新会主内存中。

另外这个队列是使用循环数组实现,所以计算下一个元素存放下标时候有些特殊。

另外insert后调用 notEmpty.signal();是为了激活调用notEmpty.await()阻塞后放入notEmpty条件队列中的线程。

四、put操作

在队列尾部添加元素,如果队列满则等待队列有空位置插入后返回

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;

    //获取可被中断锁
    lock.lockInterruptibly();
    try {

        //如果队列满,则把当前线程放入notFull管理的条件队列
        while (count == items.length)
            notFull.await();

        //插入元素
        insert(e);
    } finally {
        lock.unlock();
    }
}

需要注意的是如果队列满了那么当前线程会阻塞,直到出队操作调用了notFull.signal方法激活该线程。

代码逻辑很简单,但是这里需要思考一个问题为啥调用lockInterruptibly方法而不是Lock方法。

我的理解是因为调用了条件变量的await()方法,而await()方法会在中断标志设置后抛出InterruptedException异常后退出,所以还不如在加锁时候先看中断标志是不是被设置了,如果设置了直接抛出InterruptedException异常,就不用再去获取锁了。

然后看了其他并发类里面凡是调用了await的方法获取锁时候都是使用的lockInterruptibly方法而不是Lock也验证了这个想法。

五、poll操作

从队头获取并移除元素,队列为空,则返回null。

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //当前队列为空则返回null,否者
        return (count == 0) ? null : extract();
    } finally {
        lock.unlock();
    }
}

private E extract() {
    final Object[] items = this.items;

    //获取元素值
    E x = this.<E>cast(items[takeIndex]);

    //数组中值值为null;
    items[takeIndex] = null;

    //队头指针计算,队列元素个数减一
    takeIndex = inc(takeIndex);
    --count;

    //发送信号激活notFull条件队列里面的线程
    notFull.signal();
    return x;
}

六、take操作
从队头获取元素,如果队列为空则阻塞直到队列有元素。

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {

        //队列为空,则等待,直到队列有元素
        while (count == 0)
            notEmpty.await();
        return extract();
    } finally {
        lock.unlock();
    }
}

需要注意的是如果队列为空,当前线程会被挂起放到notEmpty的条件队列里面,直到入队操作执行调用notEmpty.signal后当前线程才会被激活,await才会返回。

七、peek操作

返回队列头元素但不移除该元素,队列为空,返回null

public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //队列为空返回null,否者返回头元素
        return (count == 0) ? null : itemAt(takeIndex);
    } finally {
        lock.unlock();
    }
}

final E itemAt(int i) {
    return this.<E>cast(items[i]);
}

八、 size操作

获取队列元素个数,非常精确因为计算size时候加了独占锁,其他线程不能入队或者出队或者删除元素

public int size() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return count;
    } finally {
        lock.unlock();
    }
}

九、总结

ArrayBlockingQueue通过使用全局独占锁实现同时只能有一个线程进行入队或者出队操作。

这个锁的粒度比较大,有点类似在方法上添加synchronized的意味。

其中offer,poll操作通过简单的加锁进行入队出队操作。

而put,take则使用了条件变量实现如果队列满则等待,如果队列空则等待,然后分别在出队和入队操作中发送信号激活等待线程实现同步。

另外相比LinkedBlockingQueue,ArrayBlockingQueue的size操作的结果是精确的,因为计算前加了全局锁。

(十五)java多线程之并发集合ArrayBlockingQueue

本人邮箱: kco1989@qq.com 欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco github: https://github.com/...
  • tianshi_kco
  • tianshi_kco
  • 2016-11-03 21:18:25
  • 5209

数组阻塞队列 ArrayBlockingQueue

1.本文目录本文目录 开篇明志 分析 构造函数及其初始化 阅读文献2.开篇明志ArrayBlockingQueue 是BlockingQueue 接口的实现类。本文简短的总结一下 ArrayBlock...
  • xidiancoder
  • xidiancoder
  • 2017-05-28 15:39:25
  • 609

Java阻塞队列ArrayBlockingQueue和LinkedBlockingQueue实现原理分析(还没看,先马)

转自:Java阻塞队列ArrayBlockingQueue和LinkedBlockingQueue实现原理分析Java中的阻塞队列接口BlockingQueue继承自Queue接口。BlockingQ...
  • x_i_y_u_e
  • x_i_y_u_e
  • 2016-09-12 14:16:31
  • 2612

Java常用的2种阻塞队列ArrayBlockingQueue和LinkedBlockingQueue

ArrayBlockingQueue       基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长...
  • chichuduxing
  • chichuduxing
  • 2017-04-05 19:11:24
  • 304

java并发-使用内置条件队列实现简单的有界缓存

内置锁和内置条件队列一起,一个简单的应用是创建可阻塞的有界缓存区,java并发包的BlockingQueue就是一个利用Lock和显式条件队列实现的可阻塞的有界队列。总结内置锁和内置条件的原理,这里我...
  • wojiushiwo945you
  • wojiushiwo945you
  • 2014-12-30 09:51:49
  • 3204

ArrayBlockingQueue跟LinkedBlockingQueue的区别

1.队列中的锁的实现不同        ArrayBlockingQueue中的锁是没有分离的,即生产和消费用的是同一个锁;        LinkedBlockingQueue中的锁是分离的,即...
  • z69183787
  • z69183787
  • 2015-07-21 15:32:32
  • 5500

Java同步队列(非阻塞队列与阻塞队列)——java并发容器

java同步队列,包括非阻塞队列与阻塞队列
  • sunxianghuang
  • sunxianghuang
  • 2016-07-27 18:36:45
  • 5464

并发 加锁 以及Java api自身的阻塞队列

Java里的阻塞队列 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会...
  • zhuangyalei
  • zhuangyalei
  • 2015-09-21 16:50:15
  • 2020

阻塞队列之ArrayBlockingQueue

阻塞队列 当使用阻塞队列的时候,它可能会对当前线程产生阻塞,,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒。 部分实现...
  • z83986976
  • z83986976
  • 2016-07-24 23:26:39
  • 2761

Java并发编程-阻塞队列(BlockingQueue)的实现原理

阻塞队列 (BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线...
  • chenchaofuck1
  • chenchaofuck1
  • 2016-06-13 19:37:30
  • 35056
收藏助手
不良信息举报
您举报文章:Java并发51:并发集合系列-基于独占锁+数组实现的单向阻塞有界队列ArrayBlockingQueue
举报原因:
原因补充:

(最多只允许输入30个字)