Java多线程(四)———线程池任务队列ArrayBlockingQueue

上面的一篇文章,我们介绍了线程池的拒接策列,这篇文章开始我们介绍线程池任务队列中的等待队列—— ArraBlockingQueue。

先看看ArrayBlockingQueue的继承和实现的关系的图。

在这里插入图片描述

ArrayBlockingQueue是一个有界的队列,这个队列排列元素FIFO(先进先出),其中队列元素是用一个数组来保存的,创建时指定队列的大小,创建后,容量无法改变。

从上面的图可以看到,ArrayBlockingQueue是继承了AbstractQueue类,和实现了BlockingQueue接口。

看一下AbstractQueue中的方法:

在这里插入图片描述
AbstractQueue中的方法不多,就是几个的方法定义,添加元素和移除 元素,清空队列等…

再看看BlockQueue中的方法

在这里插入图片描述

BlockingQueue这个接口中的方法也跟AbsractQueue差不多,也是向队列添加元素,移除元素,清空队列等…

为什么AbstractQueue已经定义有相同操作的方法,还要实现BlockingQueue中的方法呢?其实这些方法的操作看起来一样,但是实际上是不一样的。下面会比较这些方法的不同点。

ArrayBlockingQueue有三个构造方法:

一、创建固定容量和使用默认访问策列的ArrayBlockQueue。

public ArrayBlockingQueue(int capacity)

二、创建一个固定容量和指定访问策列的ArrayBlockQueue。

public ArrayBlockingQueue(int capacity, boolean fair)

三、创建一个固定容量,指定访问策列,和给定最初的队列中的集合的元素。

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

ps:访问策列是用ReentrantLock来实现的,flase为非公平锁,true为公平锁,默认使用的是非公平锁,如果使用公平锁(true),队列会以FIFO的顺序授予线程的访问权限。如果使用非公平锁(false),访问顺序是未指定的,但是公平锁会降低吞吐量。

先来看看ArrayBlockingQueue中几个重要的元素

    //用来存放元素的数组
    final Object[] items;

   //取出元素的下标
    int takeIndex;

    //插入元素的下标
    int putIndex;

  //队列中元素的个数
    int count;

这里只介绍两个重要的方法 enqueue()跟 dequeue(),也就是入队跟出队的方法,because这两个方法最重要。

下面介绍一下向队列添加元素的三种方法,其中add()方法是属于AbstractQueue中的,put()和offer()是BlockingQueue中的。
其中只有put()会阻塞,add()和 offer()是不阻塞的。

看一下它们的源码

put():

public void put(E e) throws InterruptedException {
      //非空检验
        checkNotNull(e);
        //当前锁
        final ReentrantLock lock = this.lock;
        //上锁,这是中断锁
        lock.lockInterruptibly();
        try {
        //当队列中元素已满的时候,阻塞。
            while (count == items.length)
            //阻塞等待
                notFull.await();
              //进队  
            enqueue(e);
        } finally {
        //解锁
            lock.unlock();
        }
    }

再看看入队的具体代码 也就是enqueue()方法:

private void enqueue(E x) {
        //获取当前队列
        final Object[] items = this.items;
        //添加进队列中
        items[putIndex] = x;
        //判断进队后的长度时候与队列长度相等。
        if (++putIndex == items.length)
        //相等就让插入元素的位置为0
            putIndex = 0;
            //队列元素个数+1
        count++;
        //唤醒其他阻塞出队的队列
        notEmpty.signal();
    }

ps:当队列元素已经满的状态下,其实这个队列变成一个环形队列,就是入队给跟出队的下标都相等。

offer()的源码,其实过程跟put()方法差不多,但是offer()方法不会阻塞等待。

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

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()方法是个抽象等方法,它并没有实现,所以最后它还是调用了子类的offer()方法。

入队的方法这么多,出队的方法肯定不止一个啦!ArrayBlockingQueue出队的方法有:poll(),take(),peek()。
这些出对方法中只有 take()方法会阻塞,poll()和 peek()方法不会。

看看take()方法的源码先。

 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() {
       //取得当前的队列
        final Object[] items = this.items;
        //取得当前出队的元素
        E x = (E) items[takeIndex];
        //将出队元素的位置置为空
        items[takeIndex] = null;
        //判断出队元素是不是最后一个
        if (++takeIndex == items.length)
        //是就回到最初的位置
            takeIndex = 0;
        //队列元素个数-1
        count--;
        //遍历器元素不为空
        if (itrs != null)
        //遍历器中的元素出队
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

poll()方法,调用 dequeue()方法。

public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

peek()方法

public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return itemAt(takeIndex); 
        } finally {
            lock.unlock();
        }
    }

poll()跟 peek()方法不一样。

poll()方法是检索并删除此队列的头,如果此队列为空,则返回 null 。

peek()方法检索但不删除此队列的头,如果此队列为空,则返回 null 。

一个是删除元素,一个是不删除元素。

上面就介绍了enqueue()方法跟dequeue()方法,ArrayBlockingQueue这个阻塞队列并不难理解,如果想了解更清楚一点就直接看源码。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值