点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试资料

题外话

前几天我在公众号后天收到一位读者的留言:

我相信也有其他朋友有类似的疑问只是没有发给我而已,借这个机会我再多说两句。

老实说作为一个原创作者非常”不容易“(至于如何不容易我就不矫情了),推广这个坦白说肯定会有,但我能保证会尽量克制(基本上都是一个月一次)。

ps:由于公众号只能看到最近五天的消息,所有后面的内容截不到了。大致的结果是我像他解释清楚了同时他也向我表达了歉意,这就可以了。

好,切入正题。

前言

较长一段时间以来我都发现不少开发者对 jdk 中的 J.U.C(java.util.concurrent)也就是 Java 并发包的使用甚少,更别谈对它的理解了;但这却也是我们进阶的必备关卡。

之前或多或少也分享过相关内容,但都不成体系;于是便想整理一套与并发包相关的系列文章。

其中的内容主要包含以下几个部分:

  • 根据定义自己实现一个并发工具。

  • JDK 的标准实现。

  • 实践案例。

基于这三点我相信大家对这部分内容不至于一问三不知。

既然开了一个新坑,就不想做的太差;所以我打算将这个列表下的大部分类都讲到。

所以本次重点讨论 ArrayBlockingQueue

自己实现

在自己实现之前先搞清楚阻塞队列的几个特点:

  • 基本队列特性:先进先出。

  • 写入队列空间不可用时会阻塞。

  • 获取队列数据时当队列为空时将阻塞。

实现队列的方式多种,总的来说就是数组和链表;其实我们只需要搞清楚其中一个即可,不同的特性主要表现为数组和链表的区别。

这里的 ArrayBlockingQueue 看名字很明显是由数组实现。

我们先根据它这三个特性尝试自己实现试试。

初始化队列

我这里自定义了一个类:ArrayQueue,它的构造函数如下:

    public ArrayQueue(int size) {items = new Object[size];}

很明显这里的 items 就是存放数据的数组;在初始化时需要根据大小创建数组。

写入队列

写入队列比较简单,只需要依次把数据存放到这个数组中即可,如下图:

但还是有几个需要注意的点:

  • 队列满的时候,写入的线程需要被阻塞。

  • 写入过队列的数量大于队列大小时需要从第一个下标开始写。

先看第一个 队列满的时候,写入的线程需要被阻塞,先来考虑下如何才能使一个线程被阻塞,看起来的表象线程卡住啥事也做不了。

有几种方案可以实现这个效果:

  • Thread.sleep(timeout)线程休眠。

  • object.wait() 让线程进入 waiting 状态。

当然还有一些 join、LockSupport.part 等不在本次的讨论范围。

阻塞队列还有一个非常重要的特性是:当队列空间可用时(取出队列),写入线程需要被唤醒让数据可以写入进去。

所以很明显 Thread.sleep(timeout)不合适,它在到达超时时间之后便会继续运行;达不到空间可用时才唤醒继续运行这个特点。

其实这样的一个特点很容易让我们想到 Java 的等待通知机制来实现线程间通信;更多线程见通信的方案可以参考这里:深入理解线程通信

所以我这里的做法是,一旦队列满时就将写入线程调用 object.wait() 进入 waiting 状态,直到空间可用时再进行唤醒。

    /*** 队列满时的阻塞锁*/private Object full = new Object();/*** 队列空时的阻塞锁*/private Object empty = new Object();

所以这里声明了两个对象用于队列满、空情况下的互相通知作用。

在写入数据成功后需要使用 empty.notify(),这样的目的是当获取队列为空时,一旦写入数据成功就可以把消费队列的线程唤醒。

这里的 wait 和 notify 操作都需要对各自的对象使用 synchronized 方法块,这是因为 wait 和 notify 都需要获取到各自的锁。

消费队列

上文也提到了:当队列为空时,获取队列的线程需要被阻塞,直到队列中有数据时才被唤醒。

代码和写入的非常类似,也很好理解;只是这里的等待、唤醒恰好是相反的,通过下面这张图可以很好理解:

总的来说就是:

  • 写入队列满时会阻塞直到获取线程消费了队列数据后唤醒写入线程

  • 消费队列空时会阻塞直到写入线程写入了队列数据后唤醒消费线程

测试

先来一个基本的测试:单线程的写入和消费。

3
123
1234
12345

通过结果来看没什么问题。


当写入的数据超过队列的大小时,就只能消费之后才能接着写入。

2019-04-09 16:24:41.040 [Thread-0] INFO  c.c.concurrent.ArrayQueueTest - [Thread-0]123
2019-04-09 16:24:41.040 [main] INFO  c.c.concurrent.ArrayQueueTest - size=3
2019-04-09 16:24:41.047 [main] INFO  c.c.concurrent.ArrayQueueTest - 1234
2019-04-09 16:24:41.048 [main] INFO  c.c.concurrent.ArrayQueueTest - 12345
2019-04-09 16:24:41.048 [main] INFO  c.c.concurrent.ArrayQueueTest - 123456

从运行结果也能看出只有当消费数据后才能接着往队列里写入数据。


而当没有消费时,再往队列里写数据则会导致写入线程被阻塞。

并发测试

三个线程并发写入300条数据,其中一个线程消费一条。

=====0
299

最终的队列大小为 299,可见线程也是安全的。

由于不管是写入还是获取方法里的操作都需要获取锁才能操作,所以整个队列是线程安全的。

ArrayBlockingQueue

下面来看看 JDK 标准的 ArrayBlockingQueue 的实现,有了上面的基础会更好理解。

初始化队列

看似要复杂些,但其实逐步拆分后也很好理解:

第一步其实和我们自己写的一样,初始化一个队列大小的数组。

第二步初始化了一个重入锁,这里其实就和我们之前使用的 synchronized 作用一致的;

只是这里在初始化重入锁的时候默认是 非公平锁,当然也可以指定为 true 使用公平锁;这样就会按照队列的顺序进行写入和消费。

更多关于 ReentrantLock 的使用和原理请参考这里:ReentrantLock 实现原理

三四两步则是创建了 notEmpty notFull 这两个条件,他的作用于用法和之前使用的 object.wait/notify 类似。

这就是整个初始化的内容,其实和我们自己实现的非常类似。

写入队列

其实会发现阻塞写入的原理都是差不多的,只是这里使用的是 Lock 来显式获取和释放锁。

同时其中的 notFull.await();notEmpty.signal(); 和我们之前使用的 object.wait/notify 的用法和作用也是一样的。

当然它还是实现了超时阻塞的 API

也是比较简单,使用了一个具有超时时间的等待方法。

消费队列

再看消费队列:

也是差不多的,一看就懂。

而其中的超时 API 也是使用了 notEmpty.awaitNanos(nanos) 来实现超时返回的,就不具体说了。

实际案例

说了这么多,来看一个队列的实际案例吧。

背景是这样的:

有一个定时任务会按照一定的间隔时间从数据库中读取一批数据,需要对这些数据做校验同时调用一个远程接口。

简单的做法就是由这个定时任务的线程去完成读取数据、消息校验、调用接口等整个全流程;但这样会有一个问题:

假设调用外部接口出现了异常、网络不稳导致耗时增加就会造成整个任务的效率降低,因为他都是串行会互相影响。

所以我们改进了方案:

其实就是一个典型的生产者消费者模型:

  • 生产线程从数据库中读取消息丢到队列里。

  • 消费线程从队列里获取数据做业务逻辑。

这样两个线程就可以通过这个队列来进行解耦,互相不影响,同时这个队列也能起到缓冲的作用。

但在使用过程中也有一些小细节值得注意。

因为这个外部接口是支持批量执行的,所以在消费线程取出数据后会在内存中做一个累加,一旦达到阈值或者是累计了一个时间段便将这批累计的数据处理掉。

但由于开发者的大意,在消费的时候使用的是 queue.take() 这个阻塞的 API;正常运行没啥问题。

可一旦原始的数据源,也就是 DB 中没数据了,导致队列里的数据也被消费完后这个消费线程便会被阻塞。

这样上一轮积累在内存中的数据便一直没机会使用,直到数据源又有数据了,一旦中间间隔较长时便可能会导致严重的业务异常。

所以我们最好是使用 queue.poll(timeout) 这样带超时时间的 api,除非业务上有明确的要求需要阻塞。

这个习惯同样适用于其他场景,比如调用 http、rpc 接口等都需要设置合理的超时时间。

总结

关于 ArrayBlockingQueue 的相关分享便到此结束,接着会继续更新其他并发容器及并发工具。

对本文有任何相关问题都可以留言讨论。

本文涉及到的所有源码:

https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/concurrent/ArrayQueue.java

热门内容:   

  • GitHub 上有个沙雕开发者,做了款斗图工具后火了...

  • 大白话带你认识Kafka

  • 如何参与一个顶级开源项目    

  • 深入理解 Spring Cloud 核心组件与底层原理

  • 最强 Java Redis 客户端

  • 微服务 2.0 技术栈选型手册

  • 为什么魂斗罗只有128KB却可以实现那么长的剧情?

  • IDEA高级用法:集成JIRA、UML类图插件、SSH、FTP、Database管理...

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

『并发包入坑指北』之阻塞队列相关推荐

  1. 『并发包入坑指北』之向大佬汇报任务

    前言 目录如下: 在面试过程中聊到并发相关的内容时,不少面试官都喜欢问这类问题: 当 N 个线程同时完成某项任务时,如何知道他们都已经执行完毕了. 这也是本次讨论的话题之一,所以本篇为『并发包入坑指北 ...

  2. Serverless实战 —— 三分钟入坑指北 Docsify + Serverless Framework 快速创建个人博客系统

    三分钟入坑指北

  3. 可视化拖拽生成小程序,傻瓜式免开发实现一款属于自己的小程序,云开发低码入坑指北

    前两天无意间知道了一种可以免开发,傻瓜式的拖拽生成小程序的一种方式.瞬间感觉作为程序员的石头哥离失业不远了.... 好在石头哥是一个爱学习的石头.所以今天就来看看这种靠拖拽生成小程序的平台是何方神圣. ...

  4. 美团·北极星开发对接避坑指北(Java)

    背景:公司做美团洗涤上门业务,本来好好的,后面美团要求接入美团·北极星订单预约接口,才能正常使用,没办法只能去接接口了,但是遇到了无数的坑,所以就有了这篇<美团·北极星开发对接避坑指北>, ...

  5. Android平台MediaCodec避坑指北

    https://www.jianshu.com/p/5d62a3cf0741 最近使用MediaCodec做编解码H264,写一点东西以免自己再次掉坑. 先说一下具体环境,使用的是,Windows10 ...

  6. 【Linux部署】Linux环境 .rar 格式文件处理工具安装使用(一波两折避坑指北)

    1.说明 要安装一个.rar格式的应用,上传 Linux 系统后发现没有解压工具,上网搜索后开始一波三折的旅程. 2.安装 2.1 跳坑 # 有小伙伴分享安装方法 yum install rar # ...

  7. 擦亮慧眼——找工作避坑指北!

    当今之计,靠谱的公司很多,但适合自己且靠谱的公司不一定多. 从找对象的角度换个思路看找工作. 一.动机要淳朴 找对象我们首先动机要淳朴,"不以结婚为目的的谈恋爱就是耍流氓"说的也大 ...

  8. Android环信爬坑指北(二)头像昵称好友备注显示

      在上一篇文章中提到了要在初始化的时候,设置用户信息提供者类--EaseUserProfileProvider,用以获取用户信息.下面我们来看一下 EaseUserProfileProvider 是 ...

  9. PaddlePaddle踩坑指北系列——Linux安装(一)

    本周我们精选出社区问答进行整理汇总,开发者在使用PaddlePaddle过程中遇到任何技术难题,都可以到PaddlePaddle公众号FAQ专栏上寻求解决方案,希望能帮助新用户在Linux安装过程中解 ...

最新文章

  1. The database returned no natively generated identity value错误解决方案
  2. c语言 图的存储邻接矩阵,数据结构之---C语言实现图的数组(邻接矩阵)存储表示...
  3. C++ url中文编码处理 编码转为UTF8 CChineseCode下载
  4. Python基础教程:线程操作(oncurrent模块)详解
  5. “2011年度IT博客大赛”支持fangmin的。请投下您最宝贵的一票
  6. 新手入门Web前端要掌握的4项基础技能
  7. eclipse启动tomcat无法访问
  8. 机器学习资料第3版,助你继续成长!
  9. 通过Java代码打开浏览器,本地文件目录以及ftp站点
  10. 模板方法(Template Method)
  11. 如何将cocos2d-x项目打包成一个.exe
  12. QQ浏览器下拉词推广是什么?
  13. matlab2c使用c++实现matlab函数系列教程-ceil函数
  14. 深入讲解防火墙的概念原理与实现
  15. 贝叶斯公式的直观理解(先验概率/后验概率)(未完)
  16. 《java websocket》之 实现
  17. php视频转码hls,GitHub - wanglimeng/ffmpeg-demo: 使用 ffmpeg 实现视频转码。
  18. Debian 7 安装vim
  19. 【历史杂谈】之《古代最美的谎言》
  20. 这些坑别踩!游戏随机地图生成开发经验分享

热门文章

  1. 解决Android5.0以后DatePicker选择时间无效的bug。
  2. 【直播】陈信达:零基础计算机视觉之机器学习基础
  3. 【MATLAB】将向量表示的多项式用字符串输出的通用函数示例
  4. 首个深度强化学习AI,能控制核聚变,成功登上《Nature》
  5. 区区几行Python代码,一分钟搞定一天工作量
  6. 黑科技抗疫,Python开发者大集结!
  7. @程序员,Python 3还有哪些未Get的潜藏技能?| 技术头条
  8. 空字符串计数、让字典可排序...Python冷知识(五)
  9. 实战:CNN+BLSTM+CTC的验证码识别从训练到部署 | 技术头条
  10. 百度SLG拿下前锤子科技CTO钱晨,还要合并小鱼在家? | 极客头条