12、BlockingQueue

认识BlockingQueue

阻塞队列,顾名思义,首先它是一个队列,而一个队列在数据结构中所起的作用大致如下图所示:
在这里插入图片描述
从上图我们可以很清楚看到,通过一个共享的队列,可以使得数据由队列的一端写入,从另外一端取出;
在这里插入图片描述
在这里插入图片描述
可以看到,BlockingQueue和List、Set接口一样是继承于Collection接口;

简要概述BlockingQueue常用的四个实现类

在这里插入图片描述

  • 1、ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的.
  • 2、LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的
  • 3、PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序.
  • 4、SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的(必须存一个,就等待取出后才能再存).
  • 其中LinkedBlockingQueue和ArrayBlockingQueue比较起来,它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue.

BlockingQueue的四组API

抛出异常不抛出异常,返回一个值阻塞(一直等待)阻塞(超时不等)
写入add()offer()put()offer(obj,time,timeUnit)
取出remove()poll()take()poll(time,timeUnit)
检查队首element()peek()--
1、抛出异常
//抛出异常
    public static void test01(){
        //创建队列,设置队列大小
        ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
        //设置三次循环写入
        for (int i = 0; i < 3; i++) {
            System.out.println("插入数据"+i+": "+queue.add(i));
        }
        queue.add(9);   //当队列满后再次存入
        //设置三次循环取出
        for (int i = 0; i < 3; i++) {
            System.out.println("移除数据"+i+": "+queue.remove(i));
        }
    }

运行结果
在这里插入图片描述

可以看到,我们运行这个方法后,程序抛出了一个异常,提示我们队列已满;

我们查看add方法的源码:

/**
     * Inserts the specified element at the tail of this queue if it is
     * possible to do so immediately without exceeding the queue's capacity,
     * returning {@code true} upon success and throwing an
     * {@code IllegalStateException} if this queue is full.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws IllegalStateException if this queue is full
     * @throws NullPointerException if the specified element is null
     */
    public boolean add(E e) {
        return super.add(e);
    }

	...


	/**         以下方法介绍的是super.add(e)
     * Inserts the specified element into this queue if it is possible to do so
     * immediately without violating capacity restrictions, returning
     * <tt>true</tt> upon success and throwing an <tt>IllegalStateException</tt>
     * if no space is currently available.
     *
     * <p>This implementation returns <tt>true</tt> if <tt>offer</tt> succeeds,
     * else throws an <tt>IllegalStateException</tt>.
     *
     * @param e the element to add
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     * @throws IllegalStateException if the element cannot be added at this
     *         time due to capacity restrictions
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null and
     *         this queue does not permit null elements
     * @throws IllegalArgumentException if some property of this element
     *         prevents it from being added to this queue
     */
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

在add方法中,介绍了,若队列已满,则会抛出一个异常表示队列已满;

2、不抛出异常,返回一个值
//不抛出异常,返回一个值
    public static void test02(){
        //创建队列,设置队列大小
        ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
        //设置三次循环写入
        for (int i = 0; i < 3; i++) {
            System.out.println("插入数据"+i+": "+queue.offer(i));
        }
        System.out.println(queue.offer(9));
        //设置三次循环取出
        for (int i = 0; i < 3; i++) {
            System.out.println("移除数据"+i+": "+queue.poll());
        }
        System.out.println(queue.poll());
    }

运行结果
在这里插入图片描述

可以看到,当队列已满,我们再向队列写入数据时,程序不会再出现异常,而是返回了一个false,告诉我们写入失败;而当队列为空,我们再从队列中移除数据,程序也会返回一个null,告诉我们队列已无法再取出值;

我们查看一下offer方法和poll方法的源码:

/**
     * Inserts the specified element at the tail of this queue if it is
     * possible to do so immediately without exceeding the queue's capacity,
     * returning {@code true} upon success and {@code false} if this queue
     * is full.  This method is generally preferable to method {@link #add},
     * which can fail to insert an element only by throwing an exception.
     *
     * @throws NullPointerException if the specified element is null
     */
    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();
        }
    }


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

在offer方法和poll方法中,对异常进行了处理;

3、一直等待
//一直等待
    public static void test03() throws InterruptedException {
        //创建队列,设置队列大小
        ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
        //设置三次循环写入
        for (int i = 0; i < 3; i++) {
            System.out.println("写入第"+i+"个数据");
            queue.put(i);
        }
        //当队列已满,继续写入
        queue.put(9);
        //设置三次循环取出
        for (int i = 0; i < 3; i++) {
            System.out.println("移除数据"+i+": "+queue.take());
        }
        //当队列已空,继续取出
        System.out.println(queue.take());
    }

运行结果
在这里插入图片描述
可以看到,程序进入阻塞状态,由于队列已满,程序就会一直等待队列有空位时进行入队操作;

我们来查看一下put方法的源码:

/**
     * Inserts the specified element at the tail of this queue, waiting
     * for space to become available if the queue is full.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    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();
        }
    }

在put方法中,很明显能看到一个await()方法;

4、超时不等
//超时不等
    public static void test04() throws InterruptedException {
        //创建队列,设置队列大小
        ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
        //设置三次循环写入
        for (int i = 0; i < 3; i++) {
            System.out.println("插入数据"+i+": "+queue.offer(i));
        }
        System.out.println(queue.offer(9,2,TimeUnit.SECONDS));
        //设置三次循环取出
        for (int i = 0; i < 3; i++) {
            System.out.println("移除数据"+i+": "+queue.poll());
        }
        System.out.println(queue.poll(2,TimeUnit.SECONDS));
    }

在这里插入图片描述

在此处,我们采用了和 2 中不抛出异常,返回一个值中一样的方法 offerpoll,只不过我们使用的是这两个方法的另一个重载方法 offer(参数1,参数2,参数3)poll(参数1,参数2),其中offer()方法和poll()方法中新增的两个参数分别代表的是时间长度和时间单位。在这个案例中,我们采用的是等待2秒的操作,当队列已满时,再向队列中插入数据,新插入的数据就会等待2秒中,如果这2秒内队列有空位可入队,那么就会执行入队操作,否则就会放弃入队,返回一个false标识入队失败。同样poll方法也是等待2秒,若超过2秒无数据可取出,就会返回一个null。

SynchronousQueue

SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。

简单来说,就是该队列只有将入队后的数据被取出后才会再继续执行入队操作,否则一直阻塞。

存取操作:

  • put(); 存入
  • take(); 取出
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"生产1");
                synchronousQueue.put(1);
                System.out.println(Thread.currentThread().getName()+"生产2");
                synchronousQueue.put(2);
                System.out.println(Thread.currentThread().getName()+"生产3");
                synchronousQueue.put(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"product").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"消费"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"消费"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"消费"+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"consumer").start();
    }
}

运行结果
在这里插入图片描述
可以看到,每次产与消费都是一一对应的,只有生产的被消费了才能继续写入队列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值