多线程学习(七)阻塞队列

阻塞队列

  • 基本概念:阻塞队列(BlockingQueue) 是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

应用场景

模拟一个场景

  • 场景:当用户注册的时候,不仅要注册也要发送奖励金,根据这个操作进行模拟

  • 原本的做法:
    在这里插入图片描述

    • 缺点:
      • 性能:在注册这个环节里面,假如添加用户需要花费 1 秒钟,增加积分需要花费 1 秒钟,那么整个注册结果的返回就可能需要大于22秒,虽然影响不是很大,但是在量比较大的时候,我们也需要做一些优化。
      • 代码耦合:添加用户和增加积分,可以认为是两个操作,也就是说,增加积分并不是注册必须要具备的功能,但是一旦增加积分这个逻辑出现异常,就会导致注册失败。这种耦合在程序设计的时候是一定要规避的。
  • 而加入阻塞队列的做法:通过异步的方式,实现奖励金发放的功能,减少用户时常
    在这里插入图片描述

总结

阻塞队列这块的应用场景,比较多的仍然是对于生产者消费者场景的应用,但是由于分布式架构的普及,是的大家更多的关注在分布式消息队列上。 所以其实如果把阻塞队列比作成分布式消息队列的话,那么所谓的生产者和消费者其实就是基于阻塞队列的解耦。另外,阻塞队列是一个fifo 的队列,所以对于希望在线程级别需要实现对目标服务的顺序访问的场景中,也可以使用

JUC中的阻塞队列

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

ArrayBlockingQueue 原理分析

  1. 构造方法:构造阻塞队列的方法,有三个

    • 第一个传递 capacity,表示数组的长度,也就是队列的长度在这里插入图片描述

    • 第二个传递 capacity 和 fair,fair 表示 :表示是否为公平的阻塞队列,默认情况下构造的是非公平的阻塞队列。
      在这里插入图片描述

    • 第三个传递 capacity ,fair 和 Collection<? extends E> c,提供了接收一个几个作为数据初始化的方法
      在这里插入图片描述

  2. add():添加元素,主要还是调用父类的add()方法
    在这里插入图片描述

    • 父类的 add()方法
      在这里插入图片描述
    • 主要还是判断offer()方法是否true,也就是通过offer()方法进行添加
      在这里插入图片描述
      • 第一步设置重入锁
      • 第二步锁上
      • 第三步进行判断,如果超过数组大小就返回false
      • 第四步如果数组中有位置就进行 enqueue()方法
        在这里插入图片描述
        • 第一步拿到数组
        • 第二步将数据放入对应下标的数组
        • 第三步判断是否到了数组的最大值
        • 第四步将队列中的元素加1
        • 第五步唤醒处于等待状态下的线程,表示当前队列中的元素不为空,如果存在消费者线程阻塞,就可以开始取出元素
      • 第五步返回true
      • 第六步执行finally 将锁释放
  3. put:put 方法和 add 方法功能一样,差异是 put 方法如果队列满了,会阻塞。
    在这里插入图片描述

    • 第一步判断这个元素是否存在
    • 第二步加入重入锁
    • 第三步获得锁,但是lock的区别是,这个方法优先允许在等待时由其他线程调用等待线程的interrupt方法来中断等待直接返回。而lock方法是尝试获得锁成功后才响应中断。
    • 第四步如果队列满了就开始循环等待,也就是阻塞
    • 第五步阻塞完成或者队列为空就添加数据
    • 第六步释放锁
  4. take:方法是一种阻塞获取队列中元素的方法,它的实现原理很简单,有就删除没有就阻塞,注意这个阻塞是可以中断的,如果队列没有数据那么就加入 notEmpty 条件队列等待 有数据就直接取走,方法结束,如果有新的 put 线程添加了数据,那么 put 操作将会唤醒 take 线程,执行 take 操作。如果队列中添加了元素,那么这个时候,会在enqueue 中调用 notempty .signal 唤醒 take 线程来获得元素
    在这里插入图片描述

    • 第一步 获取重入锁
    • 第二步 获得锁,这个锁是可以中断的
    • 第三步 如果队列没有元素,就阻塞
    • 第四步 如果队列有元素就执行 dequeue()方法,这个是出队列的方法,主要是删除队列头部的元素并发返回给客户端
      在这里插入图片描述
      • 第一步 获取元素,默认为0
      • 第二步 将这个位置的数组元素设置为null
      • 第三步 如果索引大于队列最大值,就设置为0,相当于将索引从队尾移到队首
      • 第四步 队列元素数量减一
      • 第五步 同时将数据更新到迭代器中
      • 第六步 唤醒因为队列满了的线程
      • 第七步 返回取到的元素
    • 第五步 释放锁
  5. remove:方法是移除一个指定元素。
    在这里插入图片描述

    • 第一步 如果传入的元素为空,就返回 false
    • 第二步 获得重入锁
    • 第三步 判断队列中有没有元素
    • 第四步 进行循环查找,找到元素就删除并且返回 true
    • 第五步 释放锁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值