java阻塞队列

什么是阻塞队列:

一句话总结:食堂排队打饭,就是一个阻塞队列
在这里插入图片描述

特点:

当队列是的,从队列中获取元素的操作将会被阻塞

当队列是的,从队列中添加元素的操作将会被阻塞

队列为的时候不能消费的时候不能添加就是阻塞队列

举个例子,在蛋糕店里面买面包,如果面包卖完了,那么消费者就不能买了,如果装蛋糕的柜子已经放满了,那么做蛋糕的师傅就不会再生产蛋糕了

用处:

在某些情况下会挂起线程(阻塞), 一旦条件满足,被挂起的线程又会自动被唤醒

为什么需要,好处是什么:

不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,不需要手动控制 wait 和 notify,因为这一切阻塞队列都搞好了

BlockingQueue在java类中的架构

BlockingQueue接口是Queue接口的子接口,Queue接口是Collection接口的子接口,List接口也是Collection接口的子接口,所以Queue应该和List接口具有相似的功能
在这里插入图片描述

阻塞队列中常见的实现类

ArrayBlockingQueue: 由数组结构组成的有界阻塞队列

LinkedBlockingQueue:由链表结构组成的有界 (但大小默认值为 integer.MAX_VALUE ) 阻塞队列

PriorityBlockingQueue:支持优先级排序的无界阻塞队列

DelayQueue:使用优先级队列实现的延迟无界阻塞队列

SynchronousQueue:不存储元素的阻塞队列,也既单个元素的队列

LinkedTransferQueue:由链表组成的无界阻塞队列

LinkedBlockingDeque:由链表组成的双向阻塞队列

BlockingQueue阻塞队列中的核心方法及案例

在这里插入图片描述

核心方法总结:检查的时候都是从队列的头部开始检查元素的,返回的都是队列中首个元素对象

抛出异常当阻塞队列满时,再往队列里add插入元素会抛出ILLegalStateException:Queye full,返回boolean类型
当阻塞队列空时,再往队列里remove移出元素会抛出NoSuchElementException,返回取出的对象
特殊值插入方法,成功返回ture 失败返回false,不会等待也不会抛出异常,队列满了就不能插入
移出方法,成功返回出队列的元素从队列的首位开始,不会等待也不会抛出异常,队列里面没有就返回null
一直阻塞当阻塞队列时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程 直到 put数据 or 响应中断退出,没有返回值
当阻塞队列时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用
超时退出当阻塞队列时,队列会阻塞生产者线程一定时间,超过限制的时间后,生产者线程退出
当阻塞队列时,队列会阻塞消费者线程一段时间,超过限制后,消费者线程返回null
例1:

创建一个界限长度为3的队列,如果

public class ArrayBlockingQueueDemo {
    public static void main(String[] args) {
        //创建一个界限为3的阻塞队列
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        //往blockingqueue中添加3个元素
        arrayBlockingQueue.add("a");
        arrayBlockingQueue.add("b");
        arrayBlockingQueue.add("c");
      	//添加第4个元素时,会出现 队列已满异常,类似于ArrayList中的下标越界异常
        arrayBlockingQueue.add("d");

    }
}

在这里插入图片描述

public class ArrayBlockingQueueDemo {
    public static void main(String[] args) {
        //创建一个界限为3的阻塞队列
        BlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        //往blockingqueue中添加3个元素,返回boolean类型
        arrayBlockingQueue.add("a");
        arrayBlockingQueue.add("b");
        arrayBlockingQueue.add("c");

        //检查队列中的队首元素是谁,打印结果为 a
        System.out.println(arrayBlockingQueue.element());

        //如果不指定清除的对象,那么清除的就是最先加入到队列中的元素
//        arrayBlockingQueue.remove();

        //如果指定清除的对象,那么清除的就是指定的对象
        arrayBlockingQueue.remove("a");

        //继续添加
        arrayBlockingQueue.add("d");
    }
}

常用方法:

add(), element(), remove()

例2
public class BlockingQueueDemo {
    public static void main(String[] args) {

        BlockingQueue<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.peek());

        //清除队列中的元素, 从队首开始, 并返回被清除的元素
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
      	//取出元素,如果取出失败,则返回null,而不是抛出异常
        System.out.println(blockingQueue.poll());
    }
}

在这里插入图片描述

例3
public class BlockingQueueDemo2 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
//        System.out.println("==============================");
        //添加第4个元素的时候,如果阻塞队列最多只能装3个元素的话,那么就会一直堵在这里,直到队列有位置让我把元素放进去
//        blockingQueue.put("d");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
      	//取元素的时候 也是这样,如果队列中有元素让我取,我就把元素取出来,如果没有元素让我取,我就等 直到有元素让我取
      	System.out.println(blockingQueue.take());
        System.out.println("==============================");
      
    }
}

可以看到,程序还没有结束

在这里插入图片描述

例4
public class BlockingQueueDemo3 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("b", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("c", 2L, TimeUnit.SECONDS));
        //往队列中添加元素,如果添加了2s钟都没有添加进去,那么就不插入了
//        System.out.println(blockingQueue.offer("d", 2L, TimeUnit.SECONDS));

        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        //从队列中取出元素,如果添加了2s钟都没有取出,那么就返回一个 null
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
    }
}

执行结果:

在这里插入图片描述

使用阻塞队列来验证生产者消费者模式

同时也可以验证 SynchronousQueue 是不存储元素的阻塞队列,也既单个元素的队列

/**
 * 使用阻塞队列来验证生产者消费者模式
 * 同时也可以验证 SynchronousQueue  是不存储元素的阻塞队列,也既单个元素的队列
 *
 * 思路:使用阻塞队列的put,take 方法 SynchronousQueue 每次只能往阻塞队列中添加一条记录
 * put一个元素的时候,如果想要再次put,生产者线程只能进入阻塞状态
 * take 一个元素的时候,如果想要再次take,和上面一样
 *
 * 开启2个线程,一个线程用来生产,一个线程用来消费
 * 1.生产者线程往阻塞队列中添加一个元素 , 想再次添加,自己进入阻塞状态,
 * 2.此时消费者线程开始执行,执行完成后,生产者线程马上就可以继续添加了
 *
 */
public class UseBlockingQueueWithThreadCommunicate {

    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
        new Thread(() ->{
            try {
                System.out.println("生产者添加 a 元素");
                blockingQueue.put("a");

                System.out.println("生产者添加 b 元素");
                blockingQueue.put("b");

                System.out.println("生产者添加 c 元素");
                blockingQueue.put("c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "aa").start();

        new Thread(() ->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println("消费者得到:"+blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println("消费者得到:"+blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println("消费者得到:"+blockingQueue.take());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "bb").start();
    }
}

在这里插入图片描述

对比使用Lock/阻塞队列生产者消费者模式的区别

使用Lock的方式

/**
 * 需求:
 * 一个面包房中的面包, 师傅生产一个, 消费者消费一个, 循环5次
 *
 * 知识点1. 线程进入await状态后, 线程会释放锁资源
 * 知识点2. 创建线程的时候要搞清楚 到底是2个线程,各操作5次共享资源; 还是10个线程, 个操作1次共享资源
 *         此例中 是2个线程各操作5次共享资源
 * 知识点3. decrease(), increase()方法中执行判断的时候 需要使用 while 而不是 if 目的是防止虚假唤醒
 *
 */
public class LockDemo {
    public static void main(String[] args) {
        CommonResource commonResource = new CommonResource();

        //
//        for (int i = 0; i < 5; i++) {
//            new Thread(() -> commonResource.increase(), String.valueOf(i)).start();
//            commonResource.increase();
//        }
//        for (int i = 0; i < 5; i++) {
//            new Thread(() -> commonResource.decrease(), String.valueOf(i)).start();
//        }
//##########################################################################################

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                commonResource.increase();
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                commonResource.decrease();
            }
        }, "BB").start();

    }
}


class CommonResource {

    private Integer num = 0;

    private final ReentrantLock reentrantLock = new ReentrantLock();
    private final Condition condition = reentrantLock.newCondition();

    public void increase() {
        reentrantLock.lock();
        try {
            while (num != 0) {
                System.out.println("num为" + num + " 生产者线程阻塞");
                condition.await();
            }
            num++;
            System.out.println("添加1个后 现在的值为" + num);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public void decrease() {
        reentrantLock.lock();
        try {
            while (num == 0) {
                System.out.println("num为0 消费者线程被阻塞");
                condition.await();
            }
            num--;
            System.out.println("减少1个后 现在的值为" + num);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }
}

在这里插入图片描述

使用阻塞队列的方式,结合volatile/cas/atomicInteger/blockingqueue/线程交互

/**
 * 使用阻塞队列完成 生产和消费蛋糕, 由<外部>控制生产和消费是否结束
 *
 * 知识点1. 为什么构造方法要传入BlockingQueue接口 而不是传入接口的某个实现类 -- 便于方法的扩展
 * 知识点2. 为什么 FLAG 需要用到 volatile --- 保证可见性,FLAG被修改了之后 所有线程可见, 然后关闭生产和消费
 * 知识点3. num 在本例中的作用不大, 只是把对象放入到阻塞队列中
 */
public class BlockingQueueDemo {

    public static void main(String[] args) {
        CommonResource3 commonResource3 = new CommonResource3(new ArrayBlockingQueue<>(2));
        new Thread(() -> {
            System.out.println("开始生产");
            commonResource3.add();
        }, "prod").start();

        new Thread(() -> {
            System.out.println("开始消费");
            commonResource3.des();
        }, "consumer").start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 关闭生产和消费
        commonResource3.finish();
    }

}

class CommonResource3 {

    private volatile Boolean FLAG = true;
    private AtomicInteger num = new AtomicInteger(0);

    private BlockingQueue<String> blockingQueue;

    public CommonResource3(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
        //判断传入的阻塞队列是哪一种类型
        System.out.println(blockingQueue.getClass().getName());
    }

    public void add() {
        String val;
        boolean offer;
        while (FLAG) {
            try {
                TimeUnit.SECONDS.sleep(1);
                val = String.valueOf(num.incrementAndGet());
                offer = blockingQueue.offer(val, 2L, TimeUnit.SECONDS);
                if (offer) {
                    System.out.println(Thread.currentThread().getName() + "线程 添加蛋糕, 当前蛋糕编号:" + val);
                } else {
                    System.out.println(Thread.currentThread().getName() + "生产失败");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void des() {
        String poll;
        while (FLAG) {
            try {
                TimeUnit.SECONDS.sleep(1);
                poll = blockingQueue.poll(2L, TimeUnit.SECONDS);
                if (Objects.nonNull(poll)) {
                    System.out.println(Thread.currentThread().getName() + " 消费蛋糕" + poll);
                } else {
                    FLAG = false;
                    System.out.println("关闭生产消费操作");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 外部关闭生产和消费
     */
    public void finish() {
        FLAG = false;
    }
}

在这里插入图片描述

区别:

  1. 使用Lock的方式需要手动加锁,控制生产和消费
  2. 使用阻塞队列的方式不用,直接生产/消费,然后由外部控制是否继续执行即可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值