栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java并发容器与框架的实现

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java并发容器与框架的实现

一、ConcurrentHashMap 1.1 背景 多线程中,HashMap导致程序死循环(HashMap采用拉链法解决hash冲突,当链表大于了装载因子对应的最大容量,需要重新进行散列。问题发生在这里,多线程可能同时出发rehash,形成了链表的回路);HashTable会阻塞其他线程,效率低下。ConccurentHashMap使用分段锁,把数据分段,每一段配一把锁。 1.2 结构 由Segment数组和HashEntry数组组成。Segment是一个ReentrantLock,当作锁。HashEntry用于存储键值对数据,是一个链表的结构。一个CHM里面有一个Segment数组,一个Segment里面有一个HashEntry数组。 1.3 操作 1.3.1 初始化:sengment数组、段偏移segmentShift、段掩码segmentMask
  • 根据concurrencyLevel计算出segment数组长度ssize(一个大于等于concurrencyLevel的最小2的N次方的值)
  • segmentShift等于32-N(1左移多少位等于ssize)
  • segmentMask是ssize-1
  • 默认的concurrencyLevel = 16,ssize=65536,segmentShift=32-4=28,segmentMask=65535
1.3.2 get操作:线程安全的,因为value和count字段都是volatile类型的

先对hashcode经过一次再散列(目的是减少冲突),然后使用散列值运算定位到Segment,再通过散列算法定位到具体的元素。

  • 定位Segment使用的hash算法 hash >>> segmentShift & segmentMask
  • 定位HashEntry使用的hash算法 int index = hash & (tab.length - 1)
1.3.3 put操作:需要对共享变量加锁
  • 先定位到Segment,判断是否需要对Segment里main的HashEntry扩容(判断当前Segment里面的HashEntry数组是否超过阈值,超过的话,创建一个双倍容量的数组,把原来数组的元素再散列后装入新的数组),然后再定位需要添加到HashEntry的元素的位置。
1.3.4 size操作:统计整个CHM的元素大小
  • 直接把所有segment的count相加,尝试加两次。比较前后两次的modCount是否变化(put、remove、clean操作前都会将++modCount)
二、ConcurrentlinkedQueue(非阻塞的线程安全队列、使用CAS算法)
  • 由head、tail两个节点组成,每个节点有next域,链队结构
  • 初始化情况下,head存储元素为空,tail=head
  • 尾节点:队列的最后一个节点,不一定等于tail节点
2.1 入队

示意图:

入队过程

1) 入队节点设置成当前尾节点的下一个节点
2) 如果tail的next不为null,入队的节点设置为tail。(体现出尾节点和tail的区别)

多线程入队面临的问题

当一个线程正在入队,需要获取尾节点,然后进行(1)操作,如果这时候有一个线程插队,尾节点可能变化,则需要暂停入队,重新获取新的尾节点。

实现
  1. 方法:循环+CAS
  2. 定位出尾节点(期间可能有其他线程插入新的节点)
  3. 定位成功后,通过CAS将入队的节点设置成尾节点的next节点
  4. 如果尾节点后面有多余HOPS个节点,通过CAS更新tail节点为当前的入队节点
  5. 使用HOPS的原因:减少CAS更新tail的次数,只有当节点和尾节点距离大于HOPS才更新tail为尾节点。(HOPS默认为1)
出队过程

当head有元素时,直接弹出head里的元素,当head没有元素,出队操作会更新head节点。 实现
  • 循环+CAS
  • 首先获取头节点元素,判断是否为空
  • 如果为空,表示另一个线程已经取走
  • 如果不为空,使用CAS将头节点的引用设置成null,如果CAS失败,表示另一个线程进行出队操作,更新了head节点,需要重新获取头节点,如果成功,返回头节点元素的值。
三、阻塞队列 概念:支持阻塞的插入和移除方法的队列,通常用于生产者-消费者场景 Java中插入和移除的4种处理方式
方法抛出异常返回值阻塞超时退出
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
查看element()peek()--

队列满,往里插入元素,抛出IllegalStateException
队列空,取元素,抛出NoSuchElementException

Java中的阻塞队列
  • ArrayBlockingQueue 有界,数组实现
  • linkedBlockingQueue 有界,链表实现
  • PriorityBlockingQueue 无界,支持优先级
  • DelayQueue 无界支持延时获取元素,优先队列实现
  • SynchronousQueue 不存储元素,用于传递数据。
  • linkedTransferQueue 无界,链表实现
  • linkedBlockingDeque 双向阻塞队列,链表实现
四、Fork/Join框架 工作窃取算法
  • 前提:大任务被分成互不依赖的子任务,每个子任务放到各自的队列里,每个队列创建一个单独的线程执行。
  • 概念:指的是某个线程从其他线程的队列窃取任务进行执行
  • 改进:减少任务窃取线程和被窃取任务的线程对任务的竞争,通常使用双端队列。被窃取任务的线程从双端队列头部拿任务,窃取任务的线程从队列尾部拿任务执行。
  • 原理:任务分割出来的子任务会从当前工作线程的双端队列头部进入,当一个工作线程的队列没有任务了,随机从其他工作线程的队列的尾部窃取一个任务来执行。
使用Fork/Join
  • ForkJoinTask提供执行fork和join的操作机制。通常使用集成他们的子类RecursiveAction(无返回结果的任务)或者RecursiveTask(有返回结果的任务),重写compute方法来使用。
  • ForkJoinPool 用来执行ForkJoinTask


    参考资料

【1】Java并发编程的艺术

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/343188.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号