阻塞队列ArrayBlockingQueue源码分析

什么是阻塞队列

阻塞队列是J.U.C中提供的一个阻塞工具,相信大家对于生产者消费者模式都比较熟悉,而阻塞队列就可以很好的去实现这种设计模式,来进行相应场景下的解耦。
在J.U.C中提供了7个阻塞队列(Java8)分别是

种类详述
ArrayBlockingQueue数组实现的有界阻塞队列,此队列按照FIFO原则对元素进行排序
LinkedBlockingQueue链表实现的有界阻塞队列,此队列的默认和最大长度为Integer.MAX_VALUE,此对列按照FIFO原则对元素进行排序
LinkedTransferQueue链表实现的无界阻塞队列
LinkedBlockingDeque链表实现的双向阻塞队列
PriorityBlockingQueue支持优先级排序的无界阻塞队列,默认情况下元素采取自然排序,也可以自定义实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue 时,指定构造参数Comparator来对元素进行排序
DelayQueue优先级队列实现的无界阻塞队列
SynchronousQueue不存储元素的的阻塞队列,每一个put操作必须等待一个take操作,否则不能继续添加元素

阻塞队列的操作方法

在阻塞队列中提供了四种操作方法

插入操作
方法名详述
add(e)添加元素到队列中,如果队列满了,继续插入元素会报IllegalStateException
offer(e)添加元素到队列,同时会返回元素是否插入成功的状态,如果成功返回true
put(e)当阻塞队列满了之后,生成者继续通过put添加元素,队列会一直阻塞生产者线程,直到队列可用
offer(e,time,unit)当阻塞队列满了之后继续添加元素,生产者线程会被阻塞指定时间,如果超时,则线程直接退出
移除操作
方法名详述
remove()当队列为空,调用会返回flase,如果元素移除成功则返回true
poll()当队列存在元素,则从队列中取出一个元素,如果队列为空则返回null
take()基于阻塞方式获取队列中的元素,如果队列中元素为空,则take方法一直阻塞,直到队列中有新的的数据可以消费
poll(long timeout, TimeUnit unit)带有超时机制的获取数据,如果队列为空,则等待一定时间再去获得元素返回。

ArrayBlockingQueue基本原理分析

构造方法

在ABQ中提供了三个构造方法,分别如下

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();	//初始化非满等待队列
}


public ArrayBlockingQueue(int capacity, boolean fair,
                          Collection<? extends E> c) {
    this(capacity, fair);

    final ReentrantLock lock = this.lock;
    lock.lock(); 
    try {
        int i = 0;
        try {
            for (E e : c) {
                checkNotNull(e);
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new IllegalArgumentException();
        }
        count = i;
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        lock.unlock();
    }
}

其中的参数分别是
capacity:队列长度
fair:是否是公平队列,默认非公平。
Collection e: 数据初始化参数

add方法

在add方法中是调用父类方法,在父类方法中主要是添加一个判断,来判断队列是否已经满了,最终还是调用offer方法来进行添加。

public boolean add(E e) {
    return super.add(e);
}
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}
offer方法

在offer方法中主要做了以下几个事情
1.判断数据是否为空
2.添加重入锁
3.判断队列长度,如果队列长度等于数组长度,放回false
4.队列未满,则调用enqueue将元素添加到队列中。

public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}
enqueue方法
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;	
    items[putIndex] = x;	//通过putIndex直接对数组赋值
    if (++putIndex == items.length)	//当putIndex==数组长度的时候,赋值为0
        putIndex = 0;
    count++;	//记录队列元素数
    notEmpty.signal();//唤醒处于等待状态下的线程,表示当前队列中元素不为空,如果有消费线程在阻塞,就可以开始取出元素。
}

在enqueue方法中,当putIndex==数组长度的时候,会重新赋值为0,这是因为ABQ是一个FIFO队列。

put方法

在put方法于add方法功能相同,差异是put满了会进行阻塞。

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    //这个也是获得锁,但是与lock区别是,
    //这个方法优先允许在等待时由其他线程调用interrrupt方法来中断等待,
    //直接返回flase,lock是必须获得锁成功后才能响应中断
    lock.lockInterruptibly();
   try {
        while (count == items.length)	//队列满了 等待
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}
take方法

take方法主要是获得锁,如果队列中没有元素则阻塞,有则调用dequeue。

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}
dequeue方法
private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];	//获得takeIndex处的元素
    items[takeIndex] = null;	//删除takeIndex处的元素
    if (++takeIndex == items.length)	//enqueue逻辑相同
        takeIndex = 0;
    count--;	//队列中元素数自减
    if (itrs != null)
        itrs.elementDequeued();	//更新迭代器中的元素
    notFull.signal();//触发因为队列满了导致被阻塞的队列。
    return x;
}
remove方法

移除指定元素的remove(Obj o)方法,主要逻辑也是很简单,就是查询该元素位置,然后再删除

public boolean remove(Object o) {
    if (o == null) return false;
    final Object[] items = this.items; //获取元素数
    final ReentrantLock lock = this.lock;
    lock.lock();//获取锁
    try {
        if (count > 0) {
            final int putIndex = this.putIndex;	//获取下一个要添加元素时的索引
            int i = takeIndex;	//获取当前要被移除的元素索引
            do {
                if (o.equals(items[i])) {	//从takeIndex下标开始找到要删除的元素。
                    removeAt(i);	//移除指定元素
                    return true;	//返回结果
                }	
                //当前删除索引执行加1后,判读是否与数组长度相等。
                //若为true,说明索引已到数组尽头,将i设置为0
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);//查找到最后一个元素
        }
        return false;
    } finally {
        lock.unlock();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值