网站建设一般做什么,上海交通大学毕业设计网站,国外免费的云服务器,网站jquery上传源代码1. 阻塞队列简介
1.1 阻塞队列概念
阻塞队列#xff1a;是一种特殊的队列#xff0c;具有队列先进先出的特性#xff0c;同时相较于普通队列#xff0c;阻塞队列是线程安全的#xff0c;并且带有阻塞功能#xff0c;表现形式如下#xff1a;
当队列满时是一种特殊的队列具有队列先进先出的特性同时相较于普通队列阻塞队列是线程安全的并且带有阻塞功能表现形式如下
当队列满时继续入队列就会阻塞直到有其他线程从队列中取出元素当队列空时继续出队列就会阻塞直到有其他线程往队列中插入元素
基于阻塞队列我们可以实现生产者消费者模型这在后端开发场景中是相当重要的
1.2 生产者-消费者模型优势
基于阻塞队列实现的 生产者消费者模型 具有以下两大优势
解耦合 以搜狗搜索的服务器举例用户输入搜索关键字 **美容**客户端的请求到达搜狗的入口服务器时会将请求转发到 广告服务器 和 大搜索服务器此时广告服务器返回相关广告内容大搜索服务器根据搜索算法匹配对应结果返回如果按照这种方式通信那么入口服务器需要编写两套代码分别同广告服务器和大搜索服务器进行交互并且一个严重问题是如果其中广告服务器宕机了会导致入口服务器无法正常工作进而影响大搜索服务器也无法正常工作 而引入阻塞队列后入口服务器不需要知晓广告服务器和大搜索服务器的存在只需要往阻塞队列中发送请求即可而广告服务器和大搜索服务器也不需要知道入口服务器的存在只需要从阻塞队列中取出请求处理完毕返回给阻塞队列即可并且当其中大搜索服务器宕机时不影响其他服务器以及入口服务器的正常运作
削峰填谷 如果没有阻塞队列当遇到一些突发场景例如双十一大促等客户请求量激增的时候入口服务器转发的请求量增多压力就会变大同理广告服务器和大搜索服务器处理过程复杂繁多消耗的硬件资源就会激增达到硬件瓶颈之后服务器就宕机了直观现象就是客户端发送请求服务器不会响应了 而引入阻塞队列/消息队列之后由于阻塞队列只负责存储相应的请求或者响应无需额外的业务处理因此抗压能力比广告服务器和大搜索服务器更强当客户请求量激增的时候交由阻塞队列承受而广告服务器和大搜索服务器只需要按照特定的速率进行读取并返回处理结果即可就起到了 削峰填谷 的作用 注意此处的阻塞队列在现实场景中并不是一个单纯的数据结构往往是一个基于阻塞队列的服务器程序例如消息队列MQ 2. 标准库中的阻塞队列
2.1 基本介绍
Java标准库提供了现成的阻塞队列数据结构供开发者使用即BlockingQueue接口 BlockingQueue该接口具有以下实现类
ArrayBlockingQueue基于数组实现的阻塞队列LinkedBlockingQueue基于链表实现的阻塞队列PriorityBlockingQueue带有优先级的阻塞队列
BlockingQueue方法该接口具有以下常用方法
带有阻塞功能
put向队列中入元素队列满则阻塞等待take向队列中取出元素队列空则阻塞等待
不带有阻塞功能
peek返回队头元素不取出poll返回队头元素取出offer向队列中插入元素
2.2 代码示例
/*** 测试Java标准库提供的阻塞队列实现*/
public class TestStandardBlockingQueue {private static BlockingQueueInteger queue new ArrayBlockingQueue(10);public static void main(String[] args) {// 生产者Thread t1 new Thread(() - {int i 0;while (true) {try {queue.put(i);System.out.println(生产数据 i);i;} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 消费者Thread t2 new Thread(() - {while (true) {try {Thread.sleep(1000);int ele queue.take();System.out.println(消费数据 ele);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}运行效果 我们在主线程中创建了两个线程其中t1线程作为生产者不断循环生产元素而线程t2作为消费者每隔1s消费一个数据所以我们很快看到当生产数据个数达到容量capacity时就会继续生产就会阻塞等待直到消费者线程消费数据后才可以继续入队列这样就实现了一个 生产者-消费者模型
3. 自定义实现阻塞队列
首先我们需要明确实现一个阻塞队列需要哪些步骤
首先我们需要实现一个普通队列使用锁机制将普通队列变成线程安全的通过特殊机制让该队列能够带有阻塞功能
3.1 实现普通队列
相信大家如果学过 数据结构与算法 相关课程应该对队列这种数据结构的实现并不陌生实现队列有基于数组的也有基于链表的我们此处采用基于数组实现的基于数组实现的循环队列也有以下两种方式
腾出一个空间用来判断队列空或者满使用额外的变量size用来记录当前元素的个数
我们使用第二种方式实现实现代码如下
/*** 自定义实现阻塞队列*/
public class MyBlockingQueue {private int head 0; // 头指针private int tail 0; // 尾指针private int size 0; // 当前元素个数private String[] array null;private int capacity; // 容量public MyBlockingQueue(int capacity) {this.capacity capacity;this.array new String[capacity];}/*** 入队列方法*/public void put(String elem) {if (size capacity) {// 队列已经满了return;}array[tail] elem;tail;if (tail capacity) {tail 0;}size;}/*** 出队列方法*/public String take() {// 判断队列是否为空if (size 0) {return null;}String topElem array[head];head;if (head capacity) {head 0;}size--;return topElem;}public static void main(String[] args) {MyBlockingQueue queue new MyBlockingQueue(3);queue.put(11);queue.put(22);queue.put(33);System.out.println(queue.take());System.out.println(queue.take());System.out.println(queue.take());}
}3.2 引入锁机制实现线程安全
引入synchronized关键字在原有队列实现的基础上实现线程安全代码如下
/*** 自定义实现阻塞队列*/
public class MyBlockingQueue {private int head 0; // 头指针private int tail 0; // 尾指针private int size 0; // 当前元素个数private String[] array null;private int capacity; // 容量private Object locker new Object(); // 锁对象public MyBlockingQueue(int capacity) {this.capacity capacity;this.array new String[capacity];}/*** 入队列方法*/public void put(String elem) {synchronized (locker) {if (size capacity) {// 队列已经满了return;}array[tail] elem;tail;if (tail capacity) {tail 0;}size;}}/*** 出队列方法*/public String take() {String topElem ;synchronized (locker) {// 判断队列是否为空if (size 0) {return null;}topElem array[head];head;if (head capacity) {head 0;}size--;}return topElem;}public static void main(String[] args) {MyBlockingQueue queue new MyBlockingQueue(3);queue.put(11);queue.put(22);queue.put(33);System.out.println(queue.take());System.out.println(queue.take());System.out.println(queue.take());}
}我们在put、take等关键方法上将 多个线程修改同一个变量 部分的操作进行加锁处理实现线程安全
3.3 加入阻塞功能
在普通队列的实现中如果队列满或者空我们直接使用return关键字返回但是在多线程环境下我们希望实现阻塞等待的功能这就可以使用Object类提供的wait/notify这组方法实现阻塞与唤醒机制了我们就需要考虑阻塞与唤醒的时机了 何时阻塞这个问题非常简单当队列满时入队列操作就应该阻塞等待而当队列为空时出队列操作就需要阻塞等待 何时唤醒想必大家都可以想到对于入队列操作来说只要队列不满就可以被唤醒而对于出队列操作来说队列不为空就可以被唤醒因此只要有线程调用take操作出队列那么入队列的线程就可以被唤醒而只要有线程调用put操作入队列那么出队列的线程就可以被唤醒
/*** 自定义实现阻塞队列*/
public class MyBlockingQueue {private int head 0; // 头指针private int tail 0; // 尾指针private int size 0; // 当前元素个数private String[] array null;private int capacity; // 容量private Object locker new Object(); // 锁对象public MyBlockingQueue(int capacity) {this.capacity capacity;this.array new String[capacity];}/*** 入队列方法*/public void put(String elem) {synchronized (locker) {while (size capacity) {// 队列已经满了进行阻塞try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}array[tail] elem;tail;if (tail capacity) {tail 0;}size;locker.notifyAll();}}/*** 出队列方法*/public String take() {String topElem ;synchronized (locker) {// 判断队列是否为空while (size 0) {try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}topElem array[head];head;if (head capacity) {head 0;}size--;locker.notifyAll();}return topElem;}public static void main(String[] args) {MyBlockingQueue queue new MyBlockingQueue(10);// 生产者Thread producer new Thread(() - {int i 0;while (true) {queue.put(i );System.out.println(生产元素 i);i;}});// 消费者Thread consumer new Thread(() - {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}String elem queue.take();System.out.println(消费元素 elem);}});producer.start();consumer.start();}
}我们使用wait/notify这组操作实现了阻塞/唤醒功能并且满足必须使用在synchronized关键字内部的使用条件这里有一个注意点 为什么我们将if判断条件改成了while循环呢这是需要考虑清楚的 如图所示一开始由于队列满所以生产者1进入阻塞状态释放锁然后生产者2也进入阻塞状态释放锁此时消费者消费一个元素后唤醒生产者1然后生产者1生产一个元素后记住此时队列已满继续唤醒但是此时唤醒的恰恰是 生产者2 生产者2继续执行生产元素于是就出现问题我们总结一下出现问题的原因
notifyAll是随机唤醒无法指定唤醒线程因此可能出现生产者唤醒生产者消费者唤醒消费者的情况if判定条件一经执行就无法继续判定所以生产者2被唤醒后没有再次判断当前队列是否满
于是我们的应对策略就是使用while循环当线程被唤醒使重新判断如果队列仍满入队列操作继续阻塞而队列仍空出队列操作继续阻塞Java标准也推荐我们使用 while 关键字和 wait 关键字一起使用
4. 应用场景实现生产者消费者模型
我们继续基于我们自定义实现的阻塞队列再来实现 生产者-消费者模型 代码示例主函数
public static void main(String[] args) {MyBlockingQueue queue new MyBlockingQueue(10);// 生产者Thread producer new Thread(() - {int i 0;while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}queue.put(i );System.out.println(生产元素 i);i;}});// 消费者Thread consumer new Thread(() - {while (true) {String elem queue.take();System.out.println(消费元素 elem);}});producer.start();consumer.start();
}运行效果 此时我们创建两个两个线程producer作为生产者线程每隔1s生产一个元素consumer作为消费者线程不断消费元素此时我们看到的就是消费者消费很快当阻塞队列空时就进入阻塞状态直到生产者线程生产元素后才被唤醒继续执行此时我们真正模拟实现了 阻塞队列 这样的数据结构