• 阻塞队列《——》特殊的队列(先进先出)


    所谓的阻塞队列:就是带有阻塞特性的《——》线程安全的

    1. 如果队列为空,尝试出队列,就会阻塞等待,等到队列不为空为止
    2. 如果队列为满,尝试入队列,也会阻塞等待,等到队列不为满为止

    这个东西非常有用,尤其是写多线程代码的时候,多个线程之间进行数据交互,可以使用阻塞队列来简化代码编写!

    Java标准库提供了阻塞队列的使用:

    1. import java.util.concurrent.ArrayBlockingQueue;
    2. import java.util.concurrent.BlockingDeque;
    3. import java.util.concurrent.LinkedBlockingDeque;
    4. //阻塞队列
    5. public class Main3 {
    6. public static void main(String[] args) throws InterruptedException{
    7. // BlockingDeque<>是一个接口,不能直接new,因此,可以new一个 BlockingDeque<>的实现类LinkedBlockingDeque<>()
    8. BlockingDeque<String> queue=new LinkedBlockingDeque<>();//基于链表实现
    9. //ArrayBlockingQueue<>()基于数组实现
    10. //put入队列
    11. queue.push("hello1");
    12. queue.push("hello2");
    13. //take出队列
    14. String result=null;
    15. result= queue.take();//取出队首元素
    16. System.out.println(result);
    17. result=queue.take();
    18. System.out.println(result);
    19. result=queue.take();
    20. System.out.println(result);
    21. }
    22. }

    上述代码的运行结果为:

    上述代码,入队列两次,当第三次出队列的时候,会进行阻塞!!想要解除阻塞,就需要有另一个线程往阻塞队列中放入元素!!

    编写一个“生产者消费者模型”,多线程使用阻塞队列

    案列:包饺子~

    生产者消费者模型:初心是啥??能解决啥问题??

    能解决的问题有很多,最主要的是俩方面:

    1.可以让上下游模块之间进行更好的“解耦合

    耦合:两个模块之间的关联关系是强还是弱(关联越强,耦合越高)

    写代码的要追求低耦合,避免代码牵一发动全身

    内聚:

    低内聚,相关联的东西没有放到一起,随便乱放的,

               相关联的代码没有放到一起,东一块西一块,

    高内聚:相关联的代码,分门别类的规制起来

    如:两个服务器A与服务器B之间,有没有阻塞队列的情况:

    2.削峰填谷

    A收到的请求数量是和用户行为相关的

    用户行为是随机的情况,有些情况下会出现“峰值”,暴涨一波!如果A和B是直接调用的关系,A收到峰值,B也同样收到峰值!假设A平时收到的请求是1秒1W个,突然间A收到了1秒5W个请求,则B也会1秒出现5W个请求,此时如果B设计的时候,没有考虑峰值,可能会直接挂了~~

    如果有了阻塞队列这种情况:此时A收到的请求多了,队列里的元素也就多了,此时A仍然可以按照之前的速率来取元素,则队列帮B承担了压力!

    比如:三峡大坝的闸门《——》削峰填谷

    接下来就编写一个“生产者消费者模型”多线程使用阻塞队列的情况!

    阻塞队列的简单用法:

    1. import java.util.concurrent.BlockingDeque;
    2. import java.util.concurrent.LinkedBlockingDeque;
    3. public class Main4 {
    4. public static void main(String[] args) {
    5. //阻塞队列
    6. BlockingDeque<Integer> blockingDeque=new LinkedBlockingDeque<>();
    7. //用法比较简单,重点在于如何实现一个阻塞队列
    8. //消费者
    9. Thread t1=new Thread(()->{
    10. while (true){
    11. try {
    12. //从队列中取元素
    13. int value = blockingDeque.take();
    14. System.out.println("消费元素:"+ value);
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. });
    20. t1.start();
    21. //生产者
    22. Thread t2=new Thread(()->{
    23. int value=0;
    24. while (true){
    25. try {
    26. System.out.println("生产元素:"+value);
    27. blockingDeque.put(value);
    28. value++;
    29. Thread.sleep(1000);
    30. } catch (InterruptedException e) {
    31. e.printStackTrace();
    32. }
    33. }
    34. });
    35. //上述代码,让生产者每隔1s生产一个元素
    36. //让消费者直接消费,不受限制
    37. t2.start();
    38. }
    39. }

    上述代码的运行结果为:

    重点:如何模拟写一个阻塞队列

    泛型??普通程序猿很少在工作中会用到

    一般实现库的程序员会涉及泛型

    因此再后续面试的时候,尽量不要写泛型!!(重要)

    能不用泛型就不用泛型~

    不用泛型,直接用朴素的代码,假定有存储的元素为int,基于数组来实现队列

    1. class MyBlockingQueue{
    2. private int[] items=new int[1000];//数组
    3. //约定[head,tail)队列的有效元素
    4. volatile private int head=0;//指向队首元素下标
    5. volatile private int tail=0;//指向队尾元素下标
    6. volatile private int size=0;//获取队列中的元素个数
    7. //入队列
    8. synchronized public void put(int elem) throws InterruptedException{
    9. if (size==items.length){
    10. //如果队列满了,插入失败
    11. //return;
    12. this.wait();
    13. //如果队列满了,就进行阻塞
    14. //出队列的notify()唤醒
    15. }
    16. //把新元素放到tail所在的位置上
    17. items[tail]=elem;
    18. tail++;
    19. //万一tail达到末尾,要让tail从头再来
    20. if (tail==items.length){
    21. tail=0;
    22. }
    23. //一样的写法,用:tail%items.length不高效
    24. //tail=tail%items.length;不推荐
    25. size++;
    26. this.notify();
    27. }
    28. //出队列
    29. synchronized public Integer take()throws InterruptedException{
    30. while (size==0){
    31. //return null;
    32. this.wait();
    33. //队列为空,阻塞等待
    34. //入队列的notify唤醒
    35. }
    36. int value=items[head];
    37. head++;//队首元素下标往后走
    38. if (head==items.length){
    39. head=0;
    40. }
    41. size--;
    42. this.notify();
    43. return value;
    44. }
    45. }
    46. public class Main5 {
    47. public static void main(String[] args) {
    48. MyBlockingQueue queue=new MyBlockingQueue();
    49. //消费者
    50. Thread t1=new Thread(()->{
    51. while (true){
    52. try {
    53. int value = queue.take();
    54. System.out.println("消费:"+value);
    55. Thread.sleep(1000);
    56. } catch (InterruptedException e) {
    57. e.printStackTrace();
    58. }
    59. }
    60. });
    61. //生产者
    62. Thread t2=new Thread(()->{
    63. int value=0;
    64. while (true){
    65. try {
    66. System.out.println("生产:"+value);
    67. queue.put(value);
    68. //Thread.sleep(1000);
    69. value++;
    70. } catch (InterruptedException e) {
    71. e.printStackTrace();
    72. }
    73. }
    74. });
    75. t1.start();
    76. t2.start();
    77. }
    78. }

    实现阻塞队列分为三步:

    1. 先实现一个普通队列
    2. 加上线程安全
    3. 加上阻塞功能

  • 相关阅读:
    快速排序的简单理解
    《剑指offer第二版》面试题14:剪绳子
    如何正确的关闭Redis服务器
    Nginx + Tomcat 搭建负载均衡、动态分离
    switch case 枚举常量
    SpringCloud Alibaba(六) - Seata 分布式事务锁
    java基础—String
    MySQL开发技巧——行列转换
    C#开发的OpenRA游戏之生命值
    C++向量夹角公式(带正负)
  • 原文地址:https://blog.csdn.net/weixin_64308540/article/details/132788934