最新要闻
- 楼市半年考·房企| 上半年“公开隐身”却曲线拿地,万科频繁人事换防能否解压业绩?
- 渑池县市场监管局郭晓荣获“2020-2021年度全国无偿献血奉献奖银奖”
- 2名中超球员被带走!还有1位中甲门将,名记:不涉案的俱乐部太少
- 小米MIX Fold 3终于预热了!米粉直呼“等得很煎熬”
- 真爱粉!网友花3万多元一次性购入三星全家桶
- 弯道超车?宝马中国开启L3级自动驾驶研发 最快年底上线
- 美国一公司称推出革命性“永久”电池:每天充放电3次 能用30年
- 破除手游MMO“热度魔咒”:《逆水寒》手游玩家数突破4000万
- 浙江欣海自主研发甲醇燃料加注船获CCS国内首个原则性认可
- 重庆警方打掉一组织跨境偷渡团伙
- “如果微信显示已读的话”上热搜 微信回应:放心 没有“如果”
- 《无畏契约》国服现已正式开服!所有玩家免费畅玩
- 7月12日迎来火星北半球的夏至:火星上夏季凉爽
- 美国富豪用17岁儿子血浆换血失败:疗法没检测到任何好处
- 解铃还须系铃人 马自达成功翻盘 CX-50销量暴涨726.64%
- 青海刚察再现“半河清水半河鱼”奇观
手机

英国房地产因利率上升陷入困境 房价正以2011年来最快速度下跌

宁夏评选出上半年10名“宁夏好人” 95后消防员因敬业奉献入选
- 英国房地产因利率上升陷入困境 房价正以2011年来最快速度下跌
- 宁夏评选出上半年10名“宁夏好人” 95后消防员因敬业奉献入选
- 离婚时共同债务应该怎么处理?
- 华为云盘古大模型3.0正式发布
- 支持自动长文生成,WPS AI发布:基于大语言模型的智能办公助手
- 《街头霸王6》全球总销量突破200万份 卡普空再次为其玩家送上礼物
家电
痛失网易30K之二:看你牛逼轰轰,请写一个阻塞队列
文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版为您奉上珍贵的学习资源 :
免费赠送 :《尼恩Java面试宝典》持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备免费赠送 :《尼恩技术圣经+高并发系列PDF》,帮你 实现技术自由,完成职业升级, 薪酬猛涨!加尼恩免费领免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》面试必备 + 大厂必备 +涨薪必备 加尼恩免费领免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》面试必备 + 大厂必备 +涨薪必备 加尼恩免费领免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
(资料图片)
痛失网易30K之二:看你牛逼轰轰,请写一个阻塞队列
说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如网易、极兔、有赞、希音、百度、美团的面试资格,遇到2个很重要的面试题:
第一弹:为啥要用阻塞队列,用list不行吗?
第二弹:手写一个阻塞队列
阻塞队列,是面试的绝对重点和难点。
小伙伴 第一弹没有回答好,面试官又来了第二弹要求手写一个阻塞队列,又没有写出来.....
小伙伴和尼恩说,阻塞队列虽然天天用,但是怎么实现, 还真没想过,还是要求手写.....,当时就懵逼了
于是30K的优质offer,白白就溜走了。
为了让后面的小伙伴不在同一个地方躺坑。
这里尼恩给大家做一下系统化、体系化的线程池梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。
也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典》V91版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请关注公众号【技术自由圈】领取,暗号:领电子书
1、什么是阻塞队列?
阻塞队列是一种队列,阻塞队列是一种特殊的队列。
阻塞队列是一种可以在多线程环境下使用,并且支持阻塞等待的队列。
线程 1 往阻塞队列中添加元素,当阻塞队列是满的,线程 1就会阻塞,直到队列不满
线程 2 从阻塞队列中移除元素,当阻塞队列是空的,线程 2 会阻塞,直到队列不空;
2、阻塞队列的作用
阻塞队列,也就是 BlockingQueue,它是一个接口,如代码所示:
public interface BlockingQueue extends Queue{...}
BlockingQueue 继承了 Queue 接口,是队列的一种。
Queue 和 BlockingQueue 都是在 Java 5 中加入的。
BlockingQueue 是线程安全的,在很多场景下都可以利用线程安全的队列来优雅地解决业务自身的线程安全问题。
比如说,使用生产者/消费者模式的时候,生产者只需要往队列里添加元素,而消费者只需要从队列里取出它们就可以了,如图所示:
阻塞队列区别于其他类型的队列的最主要的特点就是“阻塞”这两个字,
阻塞功能使得生产者和消费者两端的能力得以平衡,当有任何一端速度过快时,阻塞队列便会把过快的速度给降下来。
3、阻塞队列的核心方法
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element | peek | 不可用 | 不可用 |
1、抛异常的方法 就是在插入满了之后,会报一个异常,remove一样,element是检查队头的元素或者是否为空。2、特殊值的方法是在插入满之后返回值变成了false而不是一个异常,取出失败的时候返回null。3、阻塞方法是在插入满之后把这个方法阻塞,一直等待队列空出来一个之后再进行加入,会出现一直等待,也可能出现饥饿现象。4、超时方法的话,当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出。
实现阻塞最重要的两个方法是 take 方法和 put 方法。
3.1 take 方法
take 方法的功能是获取并移除队列的头结点,通常在队列里有数据的时候是可以正常移除的。
可是一旦执行 take 方法的时候,队列里无数据,则阻塞,直到队列里有数据。
一旦队列里有数据了,就会立刻解除阻塞状态,并且取到数据。
过程如图所示:
3.2 put 方法
put 方法插入元素时,如果队列没有满,那就和普通的插入一样是正常的插入,但是如果队列已满,那么就无法继续插入,则阻塞,直到队列里有了空闲空间。
如果后续队列有了空闲空间,比如消费者消费了一个元素,那么此时队列就会解除阻塞状态,并把需要添加的数据添加到队列中。
put 过程如图所示:
以上过程中的阻塞和解除阻塞,都是 BlockingQueue 完成的,不需要我们自己处理。
4、手写模拟实现一个阻塞队列
手写模拟实现一个阻塞队列,可以基于数组实现的阻塞队列,如何手写呢?
我们先从功能设计开始:
- 首先它是一个队列,队列需要具备入队、出队的能力, 所以,设计两个方法 put、take
- put操作的时候,需要在队列已满时,对入队的请求进行阻塞,当队列有剩余空间时,释放入队请求;
- take操作的时候,在队列为空时,需要对出队的请求进行阻塞,当队列中有元素时,释放出队请求;
- 由于ArrayBlockingQueue是一个在多线程情况下使用的数据结构,需要保证它的操作的线程安全性,所以,这里需要用到锁
4.1.用数组实现队列
如何用数组实现数据的入队出队操作呢?
如何写入呢?
这个简单,可以通过一个index字段存储当前数组下一个写入的位置。
如何处理出队呢?
一种简单的方法 :简单的返回数组第一个元素,并且把后面所有的元素向前移动一位。
如果这么操作,出队时会移动大量的元素,它的时间复杂度是O(n)。
那有没有更高效的方案呢?
还有另一个循环数组的方案,我们通过两个int字段,分别记录下一个要入队和下一个要出队的元素的位置,当入队到数组末尾时,从0开始,同样当出队到末尾时,也从0开始。
另外当队列为空和队列已满的时候,takeIndex和putIndex都指向相同的位置,所以为了进行区分,我们可以用一个count字段存储队列元素数量,这样当count=0的时候说明队列为0,count=数组容量的时候说明队列已满
4.2.使用 synchronized 实现
由于 synchronized 是同一把锁,所以使用 notify() 可能会唤醒非目标线程,notifyAll() 唤醒全部线程则会带来大量的 CPU 上下文交换和锁竞争
package com.crazymakercircle.queue;public class ArrayBlockingQueue{private Object[] array;//数组private int takeIndex;//头private int putIndex;//尾private volatile int count;//元素个数public ArrayBlockingQueue(int capacity){this.array = new Object[capacity];}//写入元素public synchronized void put(Object o) throws InterruptedException{//当队列满时,阻塞while(count == array.length){this.wait();}array[putIndex++] = o;if(putIndex ==array.length){putIndex = 0;}count++;//唤醒线程this.notifyAll();}//取出元素public synchronized Object take() throws InterruptedException{//当队列为空,阻塞while(count == 0){this.wait();}Object o = array[takeIndex++];if(takeIndex == array.length){takeIndex = 0;}count--;//唤醒线程this.notifyAll();return o;}}
4.3.使用 ReentrantLock
可以使用 Condition 指定要唤醒的线程,所以效率高
package com.crazymakercircle.queue;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;public class ArrayBlockingQueueReentrantLock{private Object[] array;//数组private int takeIndex;//头private int putIndex;//尾private volatile int count;//元素个数private ReentrantLock lock = new ReentrantLock();//锁private Condition notEmpty = lock.newCondition();//非空private Condition notFull = lock.newCondition();//非满public ArrayBlockingQueueReentrantLock(int capacity){this.array = new Object[capacity];}//写入元素public void put(Object o) throws InterruptedException{try{lock.lock();//当队列满时,阻塞while(count == array.length){notFull.wait();}array[putIndex++] = o;if(putIndex == array.length){putIndex = 0;}count++;//唤醒线程notEmpty.notifyAll();}finally{lock.unlock();}}//取出元素public Object take() throws InterruptedException{ lock.lock(); try{ //当队列为空,阻塞 while(count == 0){ notEmpty.wait(); } Object o = array[takeIndex++]; if(takeIndex == array.length){ takeIndex = 0; } count--; //唤醒线程 notFull.notifyAll(); return o; }finally{ lock.unlock(); }}}
最终,咱们要回到源码
接下来,拆解JUC源码中,ArrayBlockingQueue的实现步骤
5、拆解ArrayBlockingQueue实现步骤
我们先拆解一下问题,把拆解ArrayBlockingQueue实现步骤分成两个步骤
- 用数组实现队列
- 给队列加上阻塞能力和保证线程安全
5.1 用数组实现队列
使用 takeIndex、putIndex 避免数组复制
下面代码展示了用数组实现队列的具体实现。
class ArrayBlockingQueue { final Object[] items; int takeIndex; int putIndex; int count; public ArrayBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; } private void enqueue(E e) { Object[] items = this.items; items[putIndex] = e; if (++putIndex == items.length) putIndex = 0; count++; } private E dequeue() { Object[] items = this.items; E e = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; return e; }}
5.2 实现条件阻塞和线程安全
「在队列已满时,对入队的请求进行阻塞,当队列有剩余空间时,释放入队请求」这个需求本质上是一个条件等待的特例,写入的条件是队列不满,不满足条件的时候需要等待,直到满足条件为止。
在Java中,实现条件等待有synchronized+Object.wait和Lock+Condition.await两种方式,这里不用synchronized方案,是因为
- synchronized不支持interrupt
- synchronized无法支持多个条件
通过Lock和Condition的方案,还能够保证线程安全,因为上面的环形数组实现中,线程间共享的变量有items数组、takeIndex、putIndex、count,线程安全涉及到原子性可见性重排序几个方面,通过Lock类加锁可以对共享变量的读写操作进行保护。
定义阻塞的Lock对象和Condition,条件分为不满和不空两个条件。
class ArrayBlockingQueue { final Object[] items; int takeIndex; int putIndex; int count; ReentrantLock lock; private final Condition notEmpty; private final Condition notFull; public ArrayBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; // 创建lock对象 lock = new ReentrantLock(); // 创建非空的Condition notEmpty = lock.newCondition(); // 创建不满的Condition notFull = lock.newCondition(); }}
以入队操作添加实现为例,能够入队的条件是队列不满,也就是count < items.length,不能入队的条件反过来就是count == items.length。
当满足条件后,我们就可以入队了,入队之后,还需要唤醒等待出队的线程。
5.3 put方法的流程为
- 先加锁
- 在锁中while循环判断条件是否满足,不满足调用notFull.await(),await()方法会释放锁,被其他线程signal唤醒后会重新抢锁,再次获得锁后会继续走到while循环判断条件的地方。
- 如果条件已经满足,则执行入队操作
- 入队完之后调用notEmpty.signal()唤醒一个等待notFull条件的线程
- finally中释放锁
public void put(E e) throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); enqueue(e); notEmpty.signal(); } finally { lock.unlock(); }}
方法中还有一些小细节
- put方法中,为什么要先用一个声明一个lock局部变量呢?
ReentrantLock lock = this.lock;
这是因为如果不使用局部变量,后面所有使用实例变量的调用,在字节码指令层面需要变成先调用aload 0获取到this,再调用getField指令获取字段值,再进行其他操作。
而先把lock存到局部变量中,后面所有的获取lock就可以变成一个aload xxx指令,从而节省了指令数量,也就会加快方法的执行速度。
- 为什么while循环需要放在锁内呢?
如果不放在锁内,则可能会出现多个线程同时看到满足条件,进而去加锁入队。
虽然入队还是在临界区,但是会出现队列已满,仍然在执行入队操作的情况。
这个问题和单例的double check locking中少些一个check的问题类似。
5.4 take方法的流程为
take方法是和put相对应的出队方法,和put流程基本一致
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); E element = dequeue(); notFull.signal() return element; } finally { lock.unlock(); }}
尼恩提示
更多的 JUC 高并发知识,请参见《Java 高并发核心编程 卷2 加强版》
说在后面
如果手写到后面一个版本,并且能把 实例变量的调用的性能优化,while循环为何要放在锁内等这些高超的技术点写出来,那么太牛了。
那么面试官一定将你归为技术大佬、技术高手,面试官已经爱到 “不能自已、口水直流” 啦。
offer,当然也就来了。
此真题收入4800页《尼恩Java面试宝典》V91版,最新的PDF找尼恩获取。
技术自由的实现路径:
实现你的 架构自由:
《吃透8图1模板,人人可以做架构》
《10Wqps评论中台,如何架构?B站是这么做的!!!》
《阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了》
《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》
《100亿级订单怎么调度,来一个大厂的极品方案》
《2个大厂 100亿级 超大流量 红包 架构方案》
… 更多架构文章,正在添加中
实现你的 响应式 自由:
《响应式圣经:10W字,实现Spring响应式编程自由》
这是老版本 《Flux、Mono、Reactor 实战(史上最全)》
实现你的 spring cloud 自由:
《Spring cloud Alibaba 学习圣经》 PDF
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
实现你的 linux 自由:
《Linux命令大全:2W多字,一次实现Linux自由》
实现你的 网络 自由:
《TCP协议详解 (史上最全)》
《网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!》
实现你的 分布式锁 自由:
《Redis分布式锁(图解 - 秒懂 - 史上最全)》
《Zookeeper 分布式锁 - 图解 - 秒懂》
实现你的 王者组件 自由:
《队列之王: Disruptor 原理、架构、源码 一文穿透》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《缓存之王:Caffeine 的使用(史上最全)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》
实现你的 面试题 自由:
4000页《尼恩Java面试宝典 》 40个专题
免费获取11个技术圣经PDF:
关键词:
-
-
-
-
痛失网易30K之二:看你牛逼轰轰,请写一个阻塞队列
GPS北斗卫星时钟服务器(NTP时间服务器)助力标准化考场建设
资源成本降低70%!华为MetaERP资产核算的Serverless架构实践
人大金仓V8R6版本体验
(史上最强)打造一款强大思维导图测试用例工具《TMind用例思维导图工具》
中信证券:预计7月为本轮最后一次加息的概率较大
鹏华基金固收团队:下半年把握股债双重机遇
【财经分析】山水比德连续3个“20cm”涨停背后 业绩大幅下滑 业务深度绑定房地产
【金融街发布】中国人民银行副行长张青松:超过1000家“专精特新”中小企业在A股上市
楼市半年考·房企| 上半年“公开隐身”却曲线拿地,万科频繁人事换防能否解压业绩?
渑池县市场监管局郭晓荣获“2020-2021年度全国无偿献血奉献奖银奖”
2名中超球员被带走!还有1位中甲门将,名记:不涉案的俱乐部太少
小米MIX Fold 3终于预热了!米粉直呼“等得很煎熬”
真爱粉!网友花3万多元一次性购入三星全家桶
弯道超车?宝马中国开启L3级自动驾驶研发 最快年底上线
美国一公司称推出革命性“永久”电池:每天充放电3次 能用30年
破除手游MMO“热度魔咒”:《逆水寒》手游玩家数突破4000万
浙江欣海自主研发甲醇燃料加注船获CCS国内首个原则性认可
GPS北斗网络时钟同步器(卫星时钟发生器)插卡式模组设计方案
视频交友源码开发搭建平台用户资料功能:小功能有大用处!
决策单调性
重庆警方打掉一组织跨境偷渡团伙
“如果微信显示已读的话”上热搜 微信回应:放心 没有“如果”
《无畏契约》国服现已正式开服!所有玩家免费畅玩
7月12日迎来火星北半球的夏至:火星上夏季凉爽
美国富豪用17岁儿子血浆换血失败:疗法没检测到任何好处
解铃还须系铃人 马自达成功翻盘 CX-50销量暴涨726.64%
青海刚察再现“半河清水半河鱼”奇观
算法(施工中)
大连热电:关于重大资产重组媒体说明会情况公告
哈尔滨特产俄式风味:秋林格瓦斯1.66元/瓶大促
川浙“互宠”!浙江494家景区向四川人减免门票
凉山女孩本科4年存下16万:即将去清华园追梦
神似极氪009!沃尔沃首款纯电MPV车型EM90曝光 专供中国市场
印度北部迎强降雨 天空惊现巨型陆架云:恍如末世
得邦照明7月12日盘中涨幅达5%
【后端面经-架构】RabbitMQ简介
网评:数字产业依旧势头强劲活力充沛
四川江油暴雨:河水暴涨 多车被淹
8月见!小米MIX Fold 3入网:支持67W快充
GPT-4被破解 训练成本 模型架构的秘密都被挖出来了?
朱雀二号遥二运载火箭发射成功:全球首枚成功入轨的液氧甲烷火箭
屋顶太阳能引纠纷 特斯拉拿600多万美元和解
绝望!失踪泰坦尼克观光艇氧气耗尽,5 乘客被困漆黑冰冷深海,曾传来“砰砰”求救声
梦中仙
模板模式
手机免费在线看影片(手机m值兑换)
老板劝顾客别买了实在夸不下去了:女子试穿牛仔裤 腰间挤出两块肉
路面积水淹没车轮!郑州暴雨有多大:有些地方雨强超720 但不会持久
骑手捂住宝宝耳朵进KTV送餐 网友看完感慨:生活不易
专家建议时间类政策应鼓励男性休假:男女平等
聚焦世界人口日:我国多措并举保障妇女儿童健康权益
【读财报】房企6月融资:境内发债规模环比上升超三成
上半年汽车销量超1323万辆:出口突破200万,新能源占比近30%
对标漫威钢铁侠!DC《蓝甲虫》终极预告发布:8月18日上映
冰箱爆炸一死一伤 家家户户都有的冰箱为什么还会炸?
抢先苹果iPhone 15!荣耀Magic V2今晚发布:首次大规模用钛合金材料
富士康195亿美元建厂计划泡汤 印度无惧:完全不影响半导体雄心
SSD等暴力涨价大幕将开启:存储市场回暖 部分主控芯片供不应求
选读SQL经典实例笔记05_日期运算(下)
转型升级中的镇江实践——聚焦镇江船舶海工产业高质量发展系列报道之三
国内油价调价窗口今晚开启:或现今年首次连涨 微调1毛
不只是种菜 中国人要上太空养鱼:网友期待太空水族箱
shell脚本-批量主机执行命令(expect)
比亚迪元宇宙今日上线:身临其境看车 还能虚拟试驾
奥迪新能源车销量拉胯已沦为“杂牌” 买中国技术能行吗?
郑州暴雨深夜上热搜!官方发布红色预警:局地降水量达90.2毫米
只用一个充电口 特斯拉把车企们从美国政府那儿“拐”过来了
夏天喝什么最解渴?不是冰可乐也不是水 你肯定想不到!
钧达股份(002865):7月11日北向资金增持5.22万股
追甜剧、吃冰饮,抖音电商《夏天的风》打开品牌植入新思路
shell脚本-监控多台服务器磁盘利用率
单丹(单丹峰秦腔即兴曲)
Intel酷睿Ultra艰难冲击5GHz:核显反杀AMD!
女生回应寄顺丰快递致毕设损坏:自己做了一年 赔付952元
手机没电也能支付!数字人民币SIM卡硬钱包来了
爷爷奶奶们 别再不舍得开空调了!已有老人热到抽搐进ICU
余承东口中“1000万以内最好的SUV” 问界M9路跑视频曝光:调头超丝滑
Closest Cow Wins S 最近的奶牛获胜
代码审计工具Fortify基本使用
【快新】男高恋爱日常
国内自动驾驶芯片创业不易:卖一颗亏23万
AMD无核显锐龙i5-7500F中国特供!一优势让Intel望尘莫及
苹果客服回应FaceTime诈骗:可能是用户苹果ID泄露
电视仍持续上传 爱奇艺客户端将服务端压力转移到客户端
journalctl 清理journal日志
Docker学习路线2:底层技术
日本核污水排海 韩国居民疯狂抢购食盐 网友:今夕是何年
起诉京东方专利侵权后:三星决定将向韩企免费授权
固态硬盘价格为何会暴跌?背后原因揭开
奥迪向中国车企低头 只有比亚迪出手才能救?
支付宝4个超实用功能 3个有关你的钱 赶紧用起来
动脉血管硬化吃什么好呢
天合光能:拟出资43亿元投建13GW单晶拉棒及配套项目
上映6天 王宝强电影《八角笼中》票房破10亿:曾让周星驰看哭
高速路错过出口竟在大车前急刹被撞 女子笑称:意识到了自己的错误
《生化危机4》艾达王等身人偶展示:预售价最高18643元
奥迪被曝向中国车企购买电动平台技术 官方回应
夏日步行、骑行神器!高德地图升级“防晒导航”:可推荐阴凉路段
麻江县推行柔性执法 助推城镇精致管理行动