并发编程面试题3

一、CountDownLatch,Semaphore的高频问题:

1.1 CountDownLatch是啥?有啥用?底层咋实现的?

CountDownLatch 本质上是一个计数器,用于协调多个线程之间的同步。主要应用场景是在多线程并行处理业务时,需要等待其他线程处理完再进行后续操作,例如合并结果或响应用户请求。

用法

  • 在主线程中创建一个 CountDownLatch 对象,指定计数器的初始值。
  • 每个子线程在处理完任务后,调用 countDown 方法将计数器减1。
  • 主线程调用 await 方法,等待计数器归零后继续执行。

底层实现

  • 基于 AQS(AbstractQueuedSynchronizer)实现。
  • 创建 CountDownLatch 时,指定的数值会赋值给 state 属性。
  • 子线程调用 countDown 方法时,state 减1。
  • 当 state 归零时,调用 await 方法挂起的线程会被唤醒。

注意:CountDownLatch 不能重复使用,用完即销毁。

1.2 Semaphore是啥?有啥用?底层咋实现的?

Semaphore 是一种信号量,用于控制同时访问某个特定资源的线程数量,常用于限流。

用法

  • 在 Hystrix 中用于信号量隔离,限制并发线程数。
  • 创建 Semaphore 对象时,指定信号量的数量。
  • 每个任务提交时,获取一个信号量;任务完成后,归还信号量。

底层实现

  • 基于 AQS 实现。
  • 构建信号量时,指定信号量的资源数。
  • 获取信号量时,通过 CAS 保证原子性。
  • 归还信号量时,也通过类似的机制保证线程安全。

1.3 main线程结束,程序会停止吗?

  • 如果 main 线程结束后,仍有用户线程在执行,程序不会结束。
  • 如果 main 线程结束后,剩下的线程都是守护线程,程序会结束。

二、CopyOnWriteArrayList的高频问题:

2.1 CopyOnWriteArrayList是如何保证线程安全的?有什么缺点吗?

线程安全性

  • 写操作时,通过 ReentrantLock 保证原子性。
  • 写操作会复制一个副本,写入成功后再更新到 CopyOnWriteArrayList 的数组中。
  • 读操作不会出现数据不一致问题,因为读的是快照。

缺点

  • 写操作时需要复制副本,如果数据量大,空间占用和写入时间成本较高。
  • 不适合写操作频繁且数据量大的场景。

三、ConcurrentHashMap(JDK1.8)的高频问题:

3.1 HashMap为啥线程不安全?

  1. 在 JDK1.7 中可能出现环(扩容时)。
  2. 数据可能会覆盖和丢失。
  3. 计数器操作(如 ++)不安全,导致记录不准确。
  4. 数据迁移和扩容可能导致数据丢失。

3.2 ConcurrentHashMap如何保证线程安全的?

  1. 尾插和扩容时通过 CAS 保证线程安全。
  2. 写入数组时通过 CAS 保证安全,挂入链表或插入红黑树时通过 synchronized 保证安全。
  3. 计数器采用 LongAdder 实现,底层使用 CAS。
  4. 扩容时通过 CAS 保证数据迁移不出现并发问题,并支持并发扩容。
    在这里插入图片描述

3.3 ConcurrentHashMap构建好,数组就创建出来了吗?如果不是,如何保证初始化数组的线程安全?

  • ConcurrentHashMap 是懒加载机制,大多数框架组件都是懒加载的。
  • 通过 CAS 修改 sizeCtl 变量保证初始化线程安全,同时使用双重检查锁定(DCL)技术,外层判断数组未初始化,内层再检查一次。

3.4 为什么负载因子是0.75,为什么链表长度到8转为红黑树?

负载因子

  • 0.75 是一个平衡选择,兼顾了空间利用率和哈希冲突率。
  • 0.5:扩容太频繁,空间利用率低。
  • 1:哈希冲突频繁,查询效率低。

链表转红黑树

  • 根据泊松分布,链表长度达到8的概率非常低,保持查询效率。
  • 红黑树写入成本高,尽量规避使用。

3.5 ConcurrentHashMap何时扩容,扩容的流程是什么?

扩容时机

  1. 元素个数达到负载因子计算的阈值。
  2. 调用 putAll 方法时,如果插入的数据大于下次扩容的阈值。
  3. 数组长度小于64,且链表长度大于等于8时。
    在这里插入图片描述

扩容流程

  1. 每个扩容线程计算一个扩容标识戳。
  2. 第一个扩容线程对 sizeCtl + 2,表示一个线程在扩容。
  3. 其他扩容线程对 sizeCtl + 1,表示多个线程帮助扩容。
  4. 第一个线程初始化新数组。
  5. 每个线程领取迁移数据任务,每次长度为16。
  6. 数据迁移完毕后,线程退出扩容,对 sizeCtl - 1。
  7. 最后一个退出扩容的线程检查遗留数据,再 - 1,扩容结束。

3.6 ConcurrentHashMap得计数器如何实现的?

  • 基于 LongAdder 的机制实现,但没有直接使用 LongAdder,而是采用类似 LongAdder 的原理实现。
  • LongAdder 使用 CAS 添加,保证原子性,同时基于分段锁保证并发性。

3.7 ConcurrentHashMap的读操作会阻塞吗?

  • 读操作不会阻塞。
  • 读数组:直接返回。
  • 读链表:遍历链表查询。
  • 扩容时:如果当前索引位置数据已迁移到新数组,直接查询新数组。
  • 读红黑树:转换红黑树时,保留一个双向链表,读操作查询链表。通过 TreeBin 的 lockState 判断是否有线程在写操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖的小熊猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值