JUC并发编程(七)-- 阻塞队列BlockingQueue

什么是BlockingQueue

BlockingQueues在java.util.concurrent包下,提供了线程安全的队列访问方式,当阻塞队列插入数据时,如果队列已经满了,线程则会阻塞等待队列中元素被取出后在插入,当从阻塞队列中取数据时,如果队列是空的,则线程会阻塞等待队列中有新元素。
在这里插入图片描述

什么场景下使用?

假设我们有若干个生产线程,又有若干个消费线程。生产者利用队列的形式传递数据给消费者。
如果在某个时间段内,生产者生产数据的速度和消费者消费数据的速度不匹配的话,会如何呢?
如果生产者生产数据的速度大于消费者消费数据的速度,并且生产者生产的数据达到一定规模,就必须暂停等待一下(阻塞生产者线程),以便让消费者把积累的数据消费完,反之亦然。
这个时候我们就可以用到阻塞队列BlockingQueue,我们目前常用的线程池内部也用到了BlockingQueue

BlockingQueue的核心API

行为抛异常有返回值阻塞 等待超时
添加add(o)offer(o)put(o)offer(o, timeout, timeunit)
移除remove()poll(),remove(o)take()poll(timeout, timeunit)
判断element()peek()

行为解释:

  1. 抛异常:如果操作不能马上进行,则抛出异常
  2. 有返回值:如果操作不能马上进行,将会返回一个特殊的值,一般是true或者false
  3. 阻塞 等待:如果操作不能马上进行,操作会被阻塞
  4. 超时:如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值,一般是true或者false

方法:

  • 添加:

    • add(E e) : 添加成功返回true,失败抛IllegalStateException异常;
    • offer(E e) : 成功返回 true,如果此队列已满,则返回 false;
    • put(E e) :将元素插入此队列的尾部,如果该队列已满,则一直阻塞;
    • offer(o, timeout, timeunit):添加元素,如果队列已满,则阻塞,如果阻塞超过指定时间,就退出。
  • 移除:

    • remove(Object o) :移除指定元素,成功返回true,失败返回false;
    • remove():获取并移除队列的头元素,失败会抛NoSuchElementException异常;
    • poll() : 获取并移除此队列的头元素,若队列为空,则返回 null;
    • take():获取并移除此队列头元素,若没有元素则一直阻塞;
    • poll(timeout, timeunit):获取并移除队列头元素,若没有元素,则会阻塞,如果阻塞超过指定时间,就超时退出。
  • 判断:

    • element() :获取但不移除此队列的头元素,没有元素则抛异常;
    • peek() :获取但不移除此队列的头;若队列为空,则返回 null。

上代码:

package com.zhan.juc.blockingqueue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @Author Zhanzhan
 * @Date 2020/12/4 21:20
 * 阻塞队列
 */
public class BlockingQueueDemo {

    public static void main(String[] args) throws InterruptedException {

        // 会抛异常的方法
        ThrowTest throwTest = new ThrowTest();
//        throwTest.insert();
//        throwTest.delete();
//        throwTest.get();

        // 不会抛异常的方法
        NoThrow noThrow = new NoThrow();
//        noThrow.insert();
//        noThrow.delete();
        noThrow.get();

        // 会阻塞的方法
        Wait wait = new Wait();
        wait.insert();
    }
}

/**
 * 测试那些会抛异常的方法
 */
class ThrowTest{

    /**
     * 测试 add() , 发现会抛 java.lang.IllegalStateException
     */
    public void insert(){
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        System.out.println(blockingQueue.add("d"));
    }

    /**
     * 测试获取并移除元素 remove() ,发现会抛 java.util.NoSuchElementException
     */
    public void delete(){
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
    }

    /**
     * 获取但不移除 头元素,使用element(),失败会抛 java.util.NoSuchElementException
     */
    public void get(){
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.element());
    }
}

/**
 * 不会抛异常的方法
 */
class NoThrow{

    /**
     * 添加元素,使用 offer(),失败不会抛异常
     */
    public void insert(){
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.offer("d"));
    }

    /**
     * 获取并移除头元素,使用 poll(),失败不会抛异常
     */
    public void delete(){
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
    }

    /**
     * 获取但不移除 头元素,使用peek(),失败不会抛异常
     */
    public void get(){
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.peek());
    }
}

/**
 * 会阻塞的方法
 */
class Wait{

    /**
     * 添加元素, 使用 put(),如果队列满了,就一直阻塞
     * @throws InterruptedException
     */
    public void insert() throws InterruptedException {
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        blockingQueue.put("d");
    }

    /**
     * 移除 头元素, 如果队列为空, 就一直阻塞
     * @throws InterruptedException
     */
    public void delete() throws InterruptedException {
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.take());
    }
}

/**
 * 会超时的方法
 */
class TimeOut{

    /**
     * 添加元素, 使用 offer(),如果队列满了,就会阻塞 指定时间,超时就退出
     * @throws InterruptedException
     */
    public void insert() throws InterruptedException {
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a", 2, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("b", 2, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("c", 2, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS));
    }

    /**
     * 移除 头元素,使用 poll(), 如果队列为空, 就会阻塞指定时间, 超时则退出
     * @throws InterruptedException
     */
    public void delete() throws InterruptedException {
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
    }
}

BlockingQueue常用的实现类

一、ArrayBlockingQueue

ArrayBlockingQueue:是一个有边界的阻塞队列,它的内部是一个数组。我们必须在初始化的时候指定容量大小,一经指定就不能改变,它以FIFO先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部 。

二、LinkedBlockingQueue

LinkedBlockingQueue:是一个由链表实现的有界队列阻塞队列,但大小默认值为Integer.MAX_VALUE,如果需要的话,这一链式结构可以自定义一个上限。如果没有定义上限,将使用 Integer.MAX_VALUE 作为上限。建议指定队列大小,默认大小在添加速度大于删除速度情况下可能造成内存溢出,LinkedBlockingQueue队列也是按 FIFO(先进先出)排序元素。

三、SynchronousQueue

SynchronousQueue:没有容量,是无缓冲阻塞队列,是一个不存储元素的阻塞队列。生产者往此队列中生产一个数据,必须要等到一个消费者消费了此数据后,此队列才能继续生产添加下一个数据。
上代码:

package com.zhan.juc.blockingqueue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * @Author Zhanzhan
 * @Date 2020/12/5 15:36
 * 同步队列
 * 和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素,
 * 当生产者 往 队列中生产添加了一个数据,必须等到有消费者 从队列中取出消费了 这个数据后,此队列才能再继续添加 元素
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println("队列 " + Thread.currentThread().getName() + "生产了一个数据:1");
                blockingQueue.put("1");

                System.out.println("队列 " + Thread.currentThread().getName() + "生产了一个数据:2");
                blockingQueue.put("2");

                System.out.println("队列 " + Thread.currentThread().getName() + "生产了一个数据:3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println("线程 " + Thread.currentThread().getName() + " 消费了一个数据:" + blockingQueue.take());

                TimeUnit.SECONDS.sleep(2);
                System.out.println("线程 " + Thread.currentThread().getName() + " 消费了一个数据:" + blockingQueue.take());

                TimeUnit.SECONDS.sleep(2);
                System.out.println("线程 " + Thread.currentThread().getName() + " 消费了一个数据:" + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "消费者").start();
    }
}

运行结果:
在这里插入图片描述
符合预期!

四、ArrayBlockingQueue和LinkedBlockingQueue区别

  1. 队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
  2. 数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
  3. 由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。
  4. 两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值