Java并发编程的艺术(十二)——并发容器和框架
ConcurrentHashMap
为什么需要ConcurrentHashMap
- HashMap线程不安全,因为HashMap的Entry是以链表的形式存储的,如果多线程操作可能会形成环,那样就会死循环。
- HashTable效率低,利synchronized保证线程安全,同时只有一个线程访问其同步方法,其他线程都会被阻塞。
特点
- 锁分段技术提高并发访问率:将数据分成一段一段的,然后对每一段分别加锁,这样,两个线程在访问不同段的数据的时候,就不会出现竞争。
结构
- ConcurrentHashMap由Segment数组结构和HashEntry数组结构组成。
- 每个Segmengt包含一个HashEntry数组,并对其进行加锁操作。Segmengt继承自ReentrantLock,是可重入锁。
- HashEntry就是一个包含数据的链表。
操作
1.get
- 经过一次再散列,先定位到segment,然后再通过散列运算定位到元素。
- 不需要加锁,除非读到的值是空的才会加锁重读。
- 将共享变量定义为volatile。
2.put
- 先定位到segment,然后在segment里进行插入操作。
- 是否需要扩容?
先判断HashEntry数组是否超过阈值,如果超过了就扩容,扩容之后再插入数据(1.8就不是这样,二而是先插再扩)。而HashMap是先插入再扩容,这样不好,因为下次可能就没有数据进来了,那就白扩容了。 - 如何扩容?
先创建一个容量是原来2倍的数组,然后通过对原数组元素进行再散列后插入到新数组。扩容只会对某个segment进行。
3.size
- 如何保证多线程下安全统计
在每个segment中有一个volatile修饰的count属性,表示这个segment中的元素个数,先充实通过2次不加锁的方法统计所有count的总和,如果两次结果不相等,或者容器被修改过了,就将Segment加锁,再将进行第三次统计。 - 如何判断统计的时候ConcurrentHashMap是否被修改了?
ConcurrentHashMap中有一个modCount变量,每次put\remove\clean操作,都会对这个值加一,通过比较这个值,就知道是否容器是否被修改了。
ConcurrentLinkedQueue
是一个基于连接节点的无界线程安全队列,采用CAS算法实现。
结构
继承了AbstractQueue,不是阻塞队列。
ConcurrentLinkedQueue由head节点和tail节点组成。head、tail、next、item均使用volatile修饰,保证其内存可见性。
操作
入队
- 通过CAS的算法进行入队。
- 如果tail节点的next不为空,则将入队节点设置为tial节点,如果tail节点的next为空,则将入队节点的设置为tail的next节点。所以tail节点不总是尾节点。
- 为什么不总是让tail节点指向 尾节点?
如果将tail节点永远作为尾节点,这样每次都需要循环CAS更新tail节点,而设置一个到尾节点的距离,当tail到尾节点的距离大于某个值(通常为1)的时候再更新tail,这样可以减少更新的次数,提高入队的效率。
出队
- 不是每次出队都更新head节点,当head中有元素,就直接弹出head的元素,如果没有元素,就弹出head的next,然后更新head节点。
- 也是通过控制距离的方式,减少CAS更新节点的消耗。
阻塞队列
什么是阻塞队列
阻塞队列是支持两个阻塞操作的队列:
- 支持阻塞的插入:当队列满了,插入操作的线程就被阻塞,直到队列不满。
- 支持阻塞的移除:当队列空了,移除操作线程就会被阻塞,直到队列有元素。
阻塞队列的使用场景
- 用于消费者和生产者的场景,生产者向队列添加元素,消费者向队列读取元素。阻塞队列就是作为二者的中间缓冲。
不可用时的处理方式
JDK 提供的阻塞队列
一共七个:
- ArrayBlockingQueue
- LinkedBlockingQueue:无界
- LinkedBlockingDeque
- PriorityBlockingQueue : 无界
- DeleyQueue
- SynchronousQueue:不存储数组
- LinkedTransferQueue : 无界
ArrayBlockingQueue
是一个 数组实现的 线程安全的 有限 阻塞队列。
ArrayBlockingQueue继承自AbstractQueue,并实现了BlockingQueue接口。
ArrayBlockingQueue由ReentrantLock实现队列的互斥访问,并由notEmpty、notFull这两个Condition分别实现队空、队满的阻塞。
ReentrantLock分为公平锁和非公平锁,可以在构造ArrayBlockingQueue时指定。默认为非公平锁。
阻塞唤醒与原理
- 如果队列满:添加元素的时候,通过调用notFull.await()阻塞当前线程;移除元素额时候,用notFull.signal()唤醒在notFull上等待的线程。
- 如果队列空:读取元素的时候,通过notEmpty.await()阻塞当前线程;当添加元素时,调用notEmpty.signal()唤醒在notEmpty上等待的线程。
LinkedBlockingQueue
一共单链表实现的无界阻塞队列。
LinkedBlockingQueue继承自AbstractQueue,实现了BlockingQueue接口。
LinkedBlockingQueue由单链表实现,因此是个无限队列。但为了方式无限膨胀,构造时可以加上容量加以限制。
LinkedBlockingQueue分别采用读取锁和插入锁控制读取/删除 和 插入过程的并发访问,并采用notEmpty和notFull两个Condition实现队满队空的阻塞与唤醒。
阻塞唤醒与原理
- 如果队列满:插入元素的时候需要获取putLock,然后和上面一样,调用notFull.await(),阻塞插入线程;当队列不满了,调用signal进行唤醒,最后释放putLock。
- 如果队列空:删除获取元素需要获取takeLock,抵用await,阻塞读取线程;当队列有数据了,再调用signal唤醒线程,最后释放takeLock。
DelayQueue
是一个支持延时获取元素的无界队列。
- 使用了PriorityQueue实现。
- 队列中的元素必须实现Delayed接口,只有延时时间满了,才能提取当前元素。
应用场景
- 缓存系统设计:用DelayQueue保存缓存元素的有效期,如果能够获取到该元素,说明其有效期到了。
- 定时任务调度:用DelayQueue保存任务和任务执行时间,当获取到任务,就开始执行。
SynchronousQueue
- 不存储元素的阻塞队列。
- 每一个put必须等待另一个get,不然就不能继续添加元素。
- 支持公平访问队列。
- 适合数据需要直接传递的场景。
LinkedTransferQueue
相比其他阻塞队列,多了transfer和 tryTransfer方法。
- transfer():当消费者真在接受元素的时候,直接用transfer方法把数据传给消费者;如果没有消费者在等待,就入队。
- tryTransfer():用于是否有消费者在接受元素。
阻塞队列实现原理
利用Condition来实现的。
notEmpty = lock.newCondition();
notFull = lock.newChonditon();
- 如果队列满:添加元素的时候,通过调用notFull.await()阻塞当前线程;移除元素额时候,用notFull.signal()唤醒在notFull上等待的线程。
- 如果队列空:读取元素的时候,通过notEmpty.await()阻塞当前线程;当添加元素时,调用notEmpty.signal()唤醒在notEmpty上等待的线程。
Fork/Join框架
什么是Fork/Jion
是Java7提供的用于并行执行任务的框架,把大任务分为多个小任务,然后处理好了之后再汇总得到大任务的结果。
工作窃取算法
从某个线程中窃取任务来执行。也就是用来任务分割的算法。
- 优点:充分利用线程进行并行计算,减少线程的竞争。
- 缺点:消耗更多资源,比如创建了更多的线程和队列。
工作流程
- 分割任务
通过一个fork类把大任务分割成子任务,直到分割得足够小。 - 任务的的执行和结果的合并
分割好的子任务方法双端队列中,然后启动多个线程去队列中获取任务执行。执行的结果统一放在一个队列里,启动一个线程去合并结果。
实现原理
fork方法
- 调用pushTask方法异步地执行这个任务,然后返回结果。
- pushTask方法把当前任务放在ForkJoinTask数组队列中,然后调用signalWork方法唤醒一个工作线程来执行任务。
jion方法
用于阻塞当前线程并等待结果获取
Java并发编程的艺术(十二)——并发容器和框架的更多相关文章
- Java并发编程的艺术(十二)——线程安全
1. 什么是『线程安全』? 如果一个对象构造完成后,调用者无需额外的操作,就可以在多线程环境下随意地使用,并且不发生错误,那么这个对象就是线程安全的. 2. 线程安全的几种程度 线程安全性的前提:对『 ...
- 并发编程从零开始(十二)-Lock与Condition
并发编程从零开始(十二)-Lock与Condition 8 Lock与Condition 8.1 互斥锁 8.1.1 锁的可重入性 "可重入锁"是指当一个线程调用 object.l ...
- Java并发编程的艺术,解读并发编程的优缺点
并发编程的优缺点 使用并发的原因 多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升. 在特殊的业务场景下先天的就适合于并发编程. 比如在 ...
- 那些年读过的书《Java并发编程实战》和《Java并发编程的艺术》三、任务执行框架—Executor框架小结
<Java并发编程实战>和<Java并发编程的艺术> Executor框架小结 1.在线程中如何执行任务 (1)任务执行目标: 在正常负载情况下,服务器应用 ...
- java并发编程JUC第十二篇:AtomicInteger原子整型
AtomicInteger 类底层存储一个int值,并提供方法对该int值进行原子操作.AtomicInteger 作为java.util.concurrent.atomic包的一部分,从Java 1 ...
- Java并发编程系列之三十二:丢失的信号
这里的丢失的信号是指线程必须等待一个已经为真的条件,在開始等待之前没有检查等待条件.这样的场景事实上挺好理解,假设一边烧水,一边看电视,那么在水烧开的时候.由于太投入而没有注意到水被烧开. 丢失的信号 ...
- 转:【Java并发编程】之十二:线程间通信中notifyAll造成的早期通知问题(含代码)
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17229601 如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期 ...
- 【Java并发编程】之十二:线程间通信中notifyAll造成的早期通知问题
如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期通知,如果条件满足的时间很短,但很快又改变了,而变得不再满足,这时也将发生早期通知.这种现象听起来很奇怪,下面通过一个示例程 ...
- Java并发编程的艺术(二)——volatile、原子性
什么是volatile Java语言允许线程访问共享变量,为了确保共享变量能够被准确一致地更新,如果一个字段被声明为volatile,那么Java内存模型将会确保所有线程看到这个变量时值是一致的.保证 ...
随机推荐
- 理解 Linux 的硬链接与软链接(转)
Linux 的文件与目录 现代操作系统为解决信息能独立于进程之外被长期存储引入了文件,文件作为进程创建信息的逻辑单元可被多个进程并发使用.在 UNIX 系统中,操作系统为磁盘上的文本与图像.鼠标与键盘 ...
- ceph扩展bluestore的db分区
前言 在ceph 14版本里面才加入了bluefs-bdev-migrate,分区迁移相关的命令,那么在12版本里面其实也是可以扩展分区的 测试的版本 [root@lab102 ceph-0]# ce ...
- git-新建git用户流程-1
1.输入:https://git-scm.com/ 2.点击try git 4.注册git填写用户名和密码,邮箱,验证邮箱 5.选择免费的版本 6.创建仓库名称 创建成功见截图
- 状态模式(Established close)
状态模式(Established close) 引子 铁扇公主:以前陪我看月亮的时候,叫人家小甜甜,现在新人胜旧人了,叫人家牛夫人! 定义 Allow an object to alter its b ...
- 卷积神经网络图像纹理合成 Texture Synthesis Using Convolutional Neural Networks
代码实现 概述 这是关于Texture Synthesis Using Convolutional Neural Networks论文的tensorflow2.0代码实现,使用keras预训练的VGG ...
- SMB远程代码执行漏洞(CVE-2020-0796)分析、验证及加固
这几天有点忙,CVE-2020-0796出来了,没静下心来关注一下,显得太不尊重这个漏洞了,今天周末,关注一下,水一篇. 一.漏洞描述 漏洞公告显示,SMB 3.1.1协议 ...
- FL studio系列教程(十七):FL Studio走带面板介绍
FL Studio走带面板主要是用来控制播放.录音以及调整歌曲速度的,除此之外还可以用来选择样本剪辑.下面就来详细地看一下这部分菜单. 1.样本/歌曲模式 样本/歌曲模式主要是用来切换样本和歌曲两种模 ...
- jQuery 第四章 实例方法 DOM操作之data方法
jquery 里面 的 data 方法比较重要, 所以成一个模块写: 首先, 得知道 data() 干嘛用的, 看淘宝上 有自定义的属性, 为data - 什么什么, 这是为了dom 跟数据有 ...
- 「实验课选题详解」用C语言实现万年历
题目要求 编程实现万年历,要求: 可根据用户输入或系统日期进行初始化,如果用户无输入则显示系统日期所在月份的月历,并突出显示当前日期: 可根据用户输入的日期查询,并显示查询结果所在月份的月历,突出显示 ...
- Visual Studio 调试技巧之即时窗口的妙用
在 Visual Studio 中有一个窗口叫 Immediate 窗口,中文版本应该叫即时窗口.默认会在你启动调试时在 VS 编辑器中弹出来.你也可以通过 Debug | Windows | Imm ...