Java并发队列与容器
【前言:无论是大数据从业人员还是Java从业人员,掌握Java高并发和多线程是必备技能之一。本文主要阐述Java并发包下的阻塞队列和并发容器,其实研读过大数据相关技术如Spark、Storm等源码的,会发现它们底层大多用到了Java并发队列、同步类容器、ReentrantLock等。建议大家结合本篇文章,仔细分析一下相关源码】
BlockingQueue
阻塞队列,位于java.util.concurrent并发包下,它很好的解决了多线程中如何安全、高效的数据传输问题。所谓“阻塞”是指在某些情况下线程被挂起,当满足一定条件时会被自动唤醒,可以通过API进行控制。
常见的阻塞队列主要分为两种FIFO(先进先出)和LIFO(后进先出),当然通过不同的实现方式,还可以引申出多种不同类型的队列。首先了解一下BlockingQueue的几个核心API:put、take一对阻塞存取;add、poll一对非阻塞存取。
插入数据
put(anObj):把anObj加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻塞,直到BlockingQueue里面有空间再继续插入
add(anObj):把anObj加到BlockingQueue里,如果BlockingQueue可以容纳,则返回true,否则抛出异常
offer(anObj):表示如果可能的话,将anObj加到BlockingQueue里,如果BlockingQueue可以容纳,则返回true,否则返回false。
读取数据
take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态,直到Blocking有新的对象被加入为止
poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null
BlockingQueue核心成员介绍
ArrayBlockingQueue
基于数组实现的有界阻塞队列。因为基于数组实现,所以具有查找快,增删慢的特点。
生产者和消费者用的是同一把锁,不能并行执行效率低。它底层使用了一种标准互斥锁ReentrantLock,即读读、读写,写写都互斥,当然可以控制对象内部是否采用公平锁,默认是非公平锁。消费方式是FIFO。
生产和消费数据时,直接将枚举对象插入或删除,不会产生或销毁额外的对象实例。
应用:因为底层生产和消费用了同一把锁,定长数组不用频繁创建和销毁对象,适合于想按照队列顺序去执行任务,还不想出现频繁的GC的场景。
LinkedBlockingQueue
基于链表实现的阻塞队列,同样具有增删快,定位慢的特点。
需要注意一点:默认情况下创建的LinkedBlockingQueue容量是Integer.MAX_VALUE, 在这种情况下,如果生产者的速度一旦大于消费者的速度,可能还没有等到队列满阻塞产生,系统内存就有可能已被消耗尽。可以通过指定容量创建LinkedBlockingQueue避免这种极端情况的发生。
虽然底层使用的也是ReentrantLock但take和put是分离的(生产和消费的锁不是同一把锁),高并发场景下效率仍然高于ArrayBlockingQueue。put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
DelayQueue
DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。DelayQueue中的元素,只有指定的延迟时间到了,才能够从队列中获取到该元素。
应用场景:
1.客户端长时间占用连接的问题,超过这个空闲时间了,可以移除的
2.处理长时间不用的缓存:如果队列里面的对象长时间不用,超过空闲时间,就移除
3.任务超时处理
PriorityBlockingQueue
PriorityBlockingQueue不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此必须控制生产者生产数据的速度,避免消费者消费数据速度跟不上,否则时间一长,会最终耗尽所有的可用堆内存空间。
在向PriorityBlockingQueue中添加元素时,元素通过在实现实现Comparable接口,重写compareTo()来定义优先级的逻辑。它内部控制线程同步的锁采用的是公平锁。
SynchronousQueue
一种无缓冲的等待队列,来一个任务就执行这个任务,这期间不能添加任何的任务。也就是不用阻塞了,其实对于少量任务而言,这种做法更高效。
声明一个SynchronousQueue有两种不同的方式,公平模式和非公平模式:
公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体现整体的公平策略;
非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。
ConcurrentLinkedQueue
不上锁,高并发场景效率远高于ArrayBlockingQueue和LinkedBlockingQueue等
容器
同步类容器
第一类:Vector、Stack、HashTable都是同步类,线程安全的,但高并发场景下仍然可能出现问题如ConcurrentModificationException。
第二类:Collections提供的一些工厂类(静态),效率低

并发类容器
CopyOnWrite容器
写时复制的容器:当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行copy,复制出一个新的容器,然后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器,非常适合读多写少的场景。
但同时存在如下问题:
数据一致性问题:CopyOnWrite容器是弱一致性的,即只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据能够即时读到,不要使用CopyOnWrite容器。
内存占用问题:因为CopyOnWrite 的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。如果这些对象占用的内存比较大,如果控制不好,比如写特别多的情景,很有可能造成频繁的Yong GC 和Full GC。针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。
有两种常见的CopyOnWrite容器:CopyOnWriteArrayList和CopyOnWriteArraySet,其中CopyOnWriteArrayList是ArrayList 的一个线程安全的变体。
ConcurrentHashMap
笔者分JDK1.7和JDK1.8两部分说明ConcurrentHashMap。
JDK1.7 ConcurrentHashMap
JDK1.7采用"锁分段"技术来降低锁的粒度,它把整个map划分为一系列由segment组成的单元,一个segment相当于一个hashtable。通过这种方式,加锁的对象就从整个map变成了一个segment。ConcurrentHashMap线程安全并且提高性能原因就在于:对map中的读是并发的,无需加锁;只有在put、remove操作时才加锁,而加锁仅是对需要操作的segment加锁,不会影响其他segment的读写。因此不同的segment之间可以并发使用,极大地提高了性能。
根据源码又可得出查找、插入、删除的过程:通过key的hash确定segement(插入时如果segment大小达到扩容阈值则进行扩容) --> 确定链表数组HashEntry下标(插入/删除时,获取链表头) --> 遍历链表【查询:调用equals()进行比对,找到与所查找key相等的结点并读取;插入:如果找到相同的key的结点则更新value值,如果没有则插入新结点;删除:找到被删除结点后,以被删除结点的next结点开始建立新的链表,然后再把原链表头直到被删结点的前继结点依次复制、插入新链表,最后把新链表头设置为当前数组下标元素取代旧链表。
JDK1.8ConcurrentHashMap
JDK1.8中的ConcurrentHashMap在JDK1.7上做了很多优化:
1. 取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,通过进一步降低锁粒度来减少并发冲突的概率
2. 将原先table数组+链表的数据结构,变更为table数组+链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能
3. 新增字段transient volatile CounterCell[] counterCells,可方便的计算集合中所有元素的个数,性能大大优于jdk1.7中的size()方法
相信通过这些介绍,大家对于诸如"为什么选择ConcurrentHashMap?"会有很好的思路了。
关注微信公众号:大数据学习与分享,获取更对技术干货
Java并发队列与容器的更多相关文章
- 解读 java 并发队列 BlockingQueue
点击添加图片描述(最多60个字)编辑 今天呢!灯塔君跟大家讲: 解读 java 并发队列 BlockingQueue 最近得空,想写篇文章好好说说 java 线程池问题,我相信很多人都一知半解的,包括 ...
- 10分钟搞定 Java 并发队列好吗?好的
| 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...
- java并发:同步容器&并发容器
第一节 同步容器.并发容器 1.简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同 ...
- Java并发编程:CopyOnWrite容器的实现
Java并发编程:并发容器之CopyOnWriteArrayList(转载) 原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW ...
- java并发队列
阻塞队列 常见的阻塞队列有ArrayBlockingQueue,LinkedBlockingDeque,LinkedBlockingQueue,这些队列有界且可以阻塞线程 ArrayBlockingQ ...
- 并发编程(九)—— Java 并发队列 BlockingQueue 实现之 LinkedBlockingQueue 源码分析
LinkedBlockingQueue 在看源码之前,通过查询API发现对LinkedBlockingQueue特点的简单介绍: 1.LinkedBlockingQueue是一个由链表实现的有界队列阻 ...
- Java 并发队列 BlockingQueue
BlockingQueue 开篇先介绍下 BlockingQueue 这个接口的规则,后面再看其实现. 首先,最基本的来说, BlockingQueue 是一个先进先出的队列(Queue),为什么说是 ...
- 并发编程(八)—— Java 并发队列 BlockingQueue 实现之 ArrayBlockingQueue 源码分析
开篇先介绍下 BlockingQueue 这个接口的规则,后面再看其实现. 阻塞队列概要 阻塞队列与我们平常接触的普通队列(LinkedList或ArrayList等)的最大不同点,在于阻塞队列的阻塞 ...
- Java并发编程--同步容器
BlockingQueue 阻塞队列 对于阻塞队列,如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤 ...
随机推荐
- linux内核 idr机制
idr机制解决了什么问题?为什么需要idr机制(或者说,idr机制这种解决方案,相对已有的其他方案,有什么优势所在) ? idr在linux内核中指的就是整数ID管理机制,从本质上来说,这就是一种将整 ...
- IP基础知识
请根据IP地址 和 子网掩码,计算出 网络地址.广播地址 IP地址分类 对3类主要IP地址的补充说明:
- matlab中exist 检查变量、脚本、函数、文件夹或类的存在情况
参考: 1.https://ww2.mathworks.cn/help/matlab/ref/exist.html?searchHighlight=exist&s_tid=doc_srchti ...
- 【题解】[USACO07OPEN]Dining G
\(Link\) \(\text{Solution:}\) 这一题,我们要做到,食物和牛.牛和饮料均为一对一的关系.我们发现这个图不好建立. 经典技巧:将牛拆边,拆成入点和出点,并连容量为\(1\)的 ...
- ansible-的修改配置文件
1. ansible的配置文件 1 [root@1-230 python-2.7.5]# tree /etc/ansible/ 2 /etc/ansible/ 3 ├── ansible.cfg 4 ...
- 实验五 用PS制作图文合成海报
实验五 用PS制作图文合成海报 [实验目的] ⑴.熟悉PS软件基本操作 ⑵.学会用PS制作内容较丰富的海报式广告 [实验条件] ⑴.个人计算机一台 ⑵.个人计算机中预装Windows7操作系统和浏览 ...
- 写了多年代码,你会 StackOverflow 吗
写了多年代码,你会 StackOverflow 吗 Intro 准备写一个傻逼代码的系列文章,怎么写 StackOverflow 的代码,怎么写死锁代码,怎么写一个把 CPU 跑满,怎么写一个 Out ...
- json对象去重,根据指定字段
function FilterByName(data, Name) { //data是json对象,Name是根据什么字段去重 var map = {}, dest = []; for (var i ...
- pytest文档46-关于https请求警告问题(InsecureRequestWarning: Unverified HTTPS request is being made)
前言 使用 pytest 执行 https 请求用例的时候,控制台会出现警告:InsecureRequestWarning: Unverified HTTPS request is being mad ...
- 【C语言】这种求结构体成员大小的方法,你可能需要了解一下~
在C语言编程中,有时候需要知道某结构体中某成员的大小,比如使用堆内存来存储结构体中的某成员时,需要知道该成员的大小,才好确定所需申请的空间大小.求某结构体中某成员的大小,你会怎么做? 例子: type ...