【阻塞队列】
阻塞队列是在普通的“先进先出”队列的基础上,做出了扩充,可以组成「生产者消费者模型」
1.线程安全性
标准库是原有的队列Queue和其子类,默认都是“线程不安全的”,而阻塞队列是“线程安全的”
2.具有阻塞特性
如果队列为空,进行出队列操作,此时会出现阻塞,一直阻塞到其他线程往队列中添加元素为止;
如果队列为满,进行入队列操作,此时也会出现阻塞,一直阻塞到其他线程从队列中取走元素为止;
【生产者消费者模型】
分布式系统中可以用到“生产者消费者模型”
使用这个模型有两个好处
1.服务器之间的解耦合
降低模块之间的关联程度/影响程度,一般希望见到的是「低耦合」
阻塞队列是代码中的一个数据结构,但把这个数据结构单独封装成一个服务器程序并且在单独的服务器上进行部署时,会被称为「消息队列」
2.通过中间的阻塞队列,可以起到「削峰填谷」效果
在遇到请求量激增突发的情况下,可以有效保护下游服务器,不会被过量的请求冲垮
【一些常见问题】
1.为什么一个服务器,收到的请求更多,就可能会挂(崩溃)?
一台服务器,就是一台电脑,上面就提供了一些硬件资源(包括不限于cpu,内存,硬盘,网络带宽)
就算机器配置再好,硬件资源也是有限的
服务器每次收到一个请求,处理这个请求的过程就需要执行一系列的代码,在执行这些代码的过程中,就会需要消耗一定的硬件资源
这些请求消耗的总的硬件资源量,超过了机器能提供的上限,那么此时机器会出现问题(卡死,崩溃等)
2.在请求激增时,A为什么不会挂?队列为什么不会挂?为什么反而是B更容易挂?
A的角色是一个“网关服务器”,收到客户端的请求,再把请求转发给其他的服务器,这样的服务器里面的代码做的工作比较简单(单纯的数据转发),消耗的硬件资源通常更少,处理一个请求消耗的资源更少,同样的配置下就能支持更多的请求处理
队列也是一个简单的程序,单位请求消耗的硬件资源也是比较小的
B这个服务器是真正干活的服务器,要真正完成一系列的业务逻辑,代码量十分庞大,消耗的时间和系统硬件资源更多,同一时刻可以支撑的请求数量更少
【生产者消费者模型的代价】
1.需要更多的机器,来部署这样的消息队列
2.A和B之间通信的延时,会变长
【java自带的阻塞队列】
在java标准库中,提供现成的封装类:
BlockingQueue
这是一个接口,new出来的不会是BlockingQueue,而是其接口内所定义的几种方法
由于BlockingQueue接口继承至Queue接口,因此也可以使用Queue接口内部的方法(offer,poll等),但不建议这么做
BlockingQueue提供了另外两个专属方法,这两个方法可以阻塞,而offer和poll不能
1.put,入队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
queue.put("111");
System.out.println("put成功");
queue.put("111");
System.out.println("put成功");
queue.put("111");
System.out.println("put成功");
queue.put("111");
System.out.println("put成功");
capacity:容量,表示最多能容纳多少个元素
这个代码的执行结果为:
因为capacity限制为3,因此只能put3个元素,第四个是塞不下的,产生阻塞
2.take,出队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
queue.put("111");
System.out.println("put成功");
queue.put("111");
System.out.println("put成功");
queue.take();
System.out.println("take成功");
queue.take();
System.out.println("take成功");
queue.take();
System.out.println("take成功");
执行结果:
执行到最后一个take时,进入阻塞,因为没有元素能让它take出去了(当队列为null时,继续take会造成阻塞)