当前位置: 首页 > news >正文

多线程——面试中一个常考内容(13)

这次是多线程的最后的内容了。

上次提到了JUC的一些组件,现在我们继续。

CountDownLatch

使用多线程,经常把一个大的任务拆分成多个子任务,使用多线程执行这些子任务,从而提高程序的效率。

那么如何衡量这多个子任务的完成情况呢?整个任务都完成又怎么表示?

我们可以:

(1)CountDownLatch构造方法指定参数,描述拆成了多少个任务(这里拆成了10个子任务)

(2)每个任务执行完毕之后,都调用一次countDown方法(当一共调用了10次说明任务都完成了)

(3)主线程中调用await方法,等待所有任务执行完毕,await就会返回或阻塞等待

这里的shutdown的作用:

包括上次漏掉的get操作:

多线程下使用ArrayList

1.自行加锁(推荐)

分析清楚要把哪些代码打包到一起,成为一个“原子”操作。

2.(不推荐)

返回的List的各种关键方法都是带有synchronized

3.使用CopyOnWriteArrayList

这个偏向于不去加锁,是编程中的一种常见的思想方法,写时拷贝。

修改不同变量或读取变量时:

这里要把数组中的元素(1、2、3、4)分别修改为元素(100、200、300、400),一开始引用指向原数组,多线程先是读取,一旦某个线程进行写操作,比如修改1为100,复制一个与原数组元素相同的数组,在那里修改,复制过程中,如果其他线程在读,就直接读取旧版本的数据。

虽然复制过程不是原子的(消耗一定的时间),由于提供了旧版本的数据,不影响其他线程读取。

当新版本数组复制完毕之后,直接进行引用的修改,引用的赋值是“原子的”。

这样,就能确保读取过程中,要么读到的是旧版数据,要么读到的是新版数据,不会读到修改一半的数据,如100,200,3,4.

但是也有缺点:

1.数组特别大,非常低效

2.如果多个线程同时修改,也容易出问题

多线程使用哈希表

也是三种方法:

(1)HashMap(线程不安全)

(2)HashTable(线程安全,给各种public方法加synchronized),不推荐

(3)ConcurrentHashMap

这个效率更高,按照桶级别进行加锁,而不是给整个哈希加一个全局锁,有效降低冲突的概率。

哈希表中,我们都知道,在若干个哈希桶中,将key的值映射到数组的下标中,可是,当不同的key映射到同一个下标中时,便产生了哈希冲突。具体解决方式有两种:第一种是线性探测,这个就是另一个key的值映射到下一个下标中,这个方法基本不用;第二种就是使用链表,在每个哈希桶上挂一个链表,如果链表太长了,要么扩容,要么将链表变为红黑树。

此时Hashtable中,是对应整个数组加锁,this指向的是整个数组,此时,任意两个线程,访问任意两个不同的元素,都会产生锁竞争。

可是如果修改的两个元素,在不同的链表上,本身就不涉及线程安全问题(修改不同变量);如果修改同一个链表上的两个元素,可能有线程安全问题,比如把这两个元素插入到同一个元素后面,就可能产生竞争。

于是,我们不妨在每个哈希桶上都分别加一个不同的锁,这时如果修改不同链表上的元素,针对不同的锁对象加锁,就不会产生锁竞争(不会阻塞)。在实际开发中,用到的Hash表可能性比较大,即使多线程上访问上述的哈希表,同一时刻,两个线程恰好访问同一个链表的可能性概率就比较低。

有人问:加了这么多锁,难道不会影响性能吗?

其实分为时间开销与空间开销。Java任意一个对象都可以作为锁对象,在这个逻辑中不需要额外创建这么多对象作为锁,直接使用每个链表的头节点作为synchronized的锁对象就行了。

于是空间开销很小,时间开销虽然比HashMap大(因为他不加锁),但比起Hashtable来还是绰绰有余的。

接下来就是ConcurrentHashMap对这些的核心优化点了。

(1)把对整个表加锁变为只给桶加锁

(2)使用原子类针对size(一个链表上插入元素,另一个链表上也插入元素)进行维护

(3)针对哈希扩容的场景:化整为零、确保每个操作的加锁时间不会太长

扩容操作,意味着需要创建更大的数组,把就哈希表中的所有元素搬运到新的哈希中(此时,元素很多,耗时很长)。

假设某个插入操作,触发了扩容,进行搬运了,搬运过程中,就需要更长时间的加锁了。

一口气进行所有的搬运,比较耗时,那么,我们把整个的搬运拆成多次来完成,一旦触发扩容,不是通过一次put来完成的,而是通过多次put/get等操作完成的。

历时将近两周,终于把多线程讲完了,大家好好消化一下。

我的gitee链接:https://gitee.com/QQ2240635095/java4_11.git

https://gitee.com/QQ2240635095/java4_12.git

http://www.jsqmd.com/news/642703/

相关文章:

  • celery-redis异步任务具体应用
  • **存算一体编程新范式:用 Rust 实现高效数据流驱动的计算模型**在传统冯·诺依曼架构中,CP
  • 如何快速掌握WandEnhancer使用:面向新手的完整免费增强指南
  • linux内核 - 常用的性能分析命令
  • 以爱毕业aibiye为代表的七家专业论文辅导团队,通过优质的在线指导在国内学术服务领域脱颖而出
  • AMD Ryzen系统调试利器:SMUDebugTool实战指南
  • 基因表达预测的“权力游戏”:当转录组与表观基因组争夺控制权
  • Phi-3-mini-gguf实战:解决Web开发中常见的403 Forbidden错误
  • 智慧交通项目实战:从0到1构建一个雨天车辆行人检测系统(附VOC/YOLO格式数据集及完整代码)
  • SEPIC拓扑设计实战:从元件参数计算到PCB布局的完整指南
  • Ubuntu动态库路径管理全攻略:从LD_LIBRARY_PATH到ldconfig实战
  • # Linux服务Day04: 一站式DNS入门(原理+单域+多域+Web实战+分离解析)
  • 基于Qwen3.5-9B-AWQ-4bit的SpringBoot微服务智能开发全流程
  • 简历敢写“精通RAG“? 阿里一面挂了! 这3个夺命连环问,你能扛住几个?
  • 爱毕业aibiye及其他六家专业辅导团队,凭借高效的在线服务在国内论文指导市场占据重要地位
  • [CI/CD] 排障实录:内网环境下 Jenkins + ArgoCD 流水线搭建
  • RVC语音转换效果展示:AI歌手专辑制作全流程实录分享
  • 5分钟搞定PaddleOCR的Docker部署(附常见报错解决方案)
  • 微信直连Claude Code,多账号也能用
  • Ostrakon-VL 扫描终端 Python 入门实战:3 步实现图像数据自动化处理
  • 终极指南:如何使用Python实现百度网盘直链解析与高速下载
  • ROS手眼标定实战:JAKA机械臂+ArUco标定板全流程避坑指南
  • 微信聊天数据永久保存的终极解决方案:如何用WeChatMsg高效导出并深度分析
  • Linux 的 pathchk 命令
  • **发散创新:基于日志指标的Go语言微服务可观测性实践**在现代云原生架构中,**日志 + 指标+
  • (一)Arcpy 批量提取多面要素质心并构建空间索引
  • AI对话系统可操纵购物选择
  • 计算机组成原理知识学习助手:基于GTE-Base-ZH的问答系统
  • 别只盯着DevTools了!用OpenHarmony的HiSysEvent给你的Flutter应用做一次“线上体检”
  • bootstrap怎么实现响应式的底部固定导航栏