手写集合与并发组件

共 20 题
#

1. 手写一个线程安全的队列(锁实现与 CAS 无锁实现),各有什么取舍?

A 无锁队列一定会比锁队列吞吐更高
B CAS 无锁队列不存在 ABA 问题
C 锁队列支持阻塞式 take,无锁队列通常不阻塞而是自旋 ✓ 正确答案
D 锁队列在高竞争下一定比无锁队列性能好
#

2. 手写 ConcurrentHashMap 的简化版(分段锁/CAS+链表),要处理哪些并发细节?

A get 操作必须加锁才能保证安全
B 空槽位用 CAS 写入、非空槽位对链表头加锁,可减少竞争 ✓ 正确答案
C 分段数越多一定越安全
D 扩容时不需要处理旧表与新表的可见性
#

3. 手写无锁队列/栈(Michael-Scott Queue 或 Treiber Stack)并用 AtomicReference/CAS 实现,ABA 问题如何出现与解决?

A ABA 问题只存在于锁队列中
B 用 AtomicStampedReference 携带版本号可解决 ABA ✓ 正确答案
C ABA 问题在并发度低时不会出现
D 用 synchronized 无法避免 ABA
#

4. 手写一个 LRU 缓存(哈希表 + 双向链表)并分析 get/put 的时间复杂度与并发安全方案?

A get 需要遍历链表才能定位,时间复杂度 O(n)
B 扩容时需重新计算所有节点
C 哈希表用于 O(1) 定位,双向链表用于 O(1) 的移动与删除 ✓ 正确答案
D LRU 缓存天然线程安全
#

5. 手写 HashMap,数组+链表(+红黑树)的 put/get/resize 实现要点

A 扩容后必须重新计算所有元素的哈希值
B 扩容时链表顺序会被反转
C 负载因子越大扩容越频繁
D 1.8 扩容可用高低位拆分,元素只可能留在原索引或原索引+旧容量 ✓ 正确答案
#

6. 手写一个支持过期淘汰的本地缓存(TTL+LRU 组合)?

A TTL 处理时间过期,LRU 处理容量淘汰,二者互补 ✓ 正确答案
B LRU 可以自动清理已过期的数据
C 惰性删除能清理所有已过期项
D 过期项放在队尾即可被 LRU 正确淘汰
#

7. 手写 CopyOnWrite 思想的读写容器,说明何时复制何时共享?

A 迭代器能看到最新修改
B 读操作需要加锁保证安全
C 每次写操作都会复制整个数组 ✓ 正确答案
D 适合写多读少的场景
#

8. 手写跳表(SkipList)并说明与红黑树在并发场景下的实现取舍?

A 跳表指针更新局部化,更适合并发无锁实现 ✓ 正确答案
B 红黑树插入只影响局部指针,适合 CAS 无锁
C 跳表查找时间复杂度是 O(n)
D 跳表与红黑树都无法实现有序遍历
#

9. 手写一个原子计数器(CAS 自旋)并对比 LongAdder 的 cell 分段思路,什么场景下后者吞吐更高?

A AtomicLong 在写竞争激烈时吞吐更高
B LongAdder 读操作比 AtomicLong 更快
C LongAdder 的 sum 操作与 AtomicLong 一样是 O(1) 且无消耗
D LongAdder 用 cell 数组分散竞争,写多场景吞吐更高 ✓ 正确答案
#

10. 手写线程安全的 HashMap 简化版(分段锁 vs CAS+链表)并说明与 ConcurrentHashMap 的差距?

A 简化版已具备并发扩容能力
B 分段锁版 get 必须加锁
C 生产版还包含多线程协助扩容、树化、CounterCell 计数等能力 ✓ 正确答案
D CAS+链表版不需要处理空槽竞争
#

11. 手写 BlockingQueue,lock+condition 与锁分段两种实现的差异如何?

A 需要两个 Condition 分别表示"非空"与"非满" ✓ 正确答案
B put 满时自旋等待而非让出 CPU
C 锁分段比单锁实现更简单
D 队列满时 put 直接返回 false
#

12. 手写 ArrayList,扩容策略、迭代器与 fail-fast 的简化实现

A 扩容时按 2 倍扩充
B 迭代器通过 modCount 与 expectedModCount 比对实现 fail-fast ✓ 正确答案
C fail-fast 能保证所有并发修改一定被检测到
D ArrayList 扩容不影响原有元素位置
#

13. 手写 CopyOnWriteArrayList 的迭代器语义,为什么迭代器基于不可变快照、弱一致性体现在哪,与普通 ArrayList 迭代器的差异如何?

A 迭代器遍历时会检查并发修改并抛异常
B 迭代器能看到遍历期间的最新修改
C 迭代器基于创建时的数组快照,是弱一致性 ✓ 正确答案
D 迭代器支持就地删除元素
#

14. 手写 ConcurrentLinkedQueue 的非阻塞入队,原子 CAS 更新 tail 的延迟指针(hop)优化,为什么入队操作需要分两步?

A 入队只需一次 CAS 即可完成
B tail 永远指向真正的队尾
C tail 采用延迟推进,减少 CAS 竞争 ✓ 正确答案
D 入队失败会抛出异常
#

15. 手写并发计数器,AtomicLong 与 LongAdder 的分段思路对比,读多写多场景的适用边界如何?

A 写多读少用 LongAdder,读多写少用 AtomicLong ✓ 正确答案
B 读多写少用 LongAdder 更合适
C 两者读成本相同
D LongAdder 的 sum 总是返回最新精确值
#

16. 手写分段锁 Map 的锁粒度选择,桶级锁 vs 段级锁的扩容安全与并发度,锁粒度与内存开销如何权衡?

A 段级锁扩容需要所有段停止
B 桶级锁并发度低于段级锁
C 锁粒度越细内存开销越小
D 段级锁并发度与段数成正比 ✓ 正确答案
#

17. 手写一个阻塞队列(take/put 的等待与唤醒)并说明与 LinkedBlockingQueue 的差异?

A LinkedBlockingQueue 只能是无界队列
B LinkedBlockingQueue 用头尾两把锁可提高 put/take 并发度 ✓ 正确答案
C 手写版通常用两把锁实现
D Condition 的 await 会一直占用 CPU 自旋
#

18. 手写环形缓冲(Ring Buffer)并用 volatile 指针实现单生产者单消费者无锁队列,容量为何取 2 的幂?

A 让数组更小
B 满足垃圾回收要求
C 让数组初始化为零
D 用位运算代替取模,提高回绕性能 ✓ 正确答案
#

19. 手写 DelayQueue 简化版(优先队列 + 条件等待)并说明与 ScheduledThreadPoolExecutor 的关系?

A PriorityQueue 按插入顺序排序
B DelayQueue take 时总是立即返回队首
C ScheduledThreadPoolExecutor 内部使用 DelayQueue 变体作为任务队列 ✓ 正确答案
D DelayQueue 无法实现延迟等待
#

20. 手写对象池,借用/归还、池满策略与泄漏检测

A acquire 时若池空则必须阻塞等待
B 对象池不需要考虑空池情况
C 池满时归还对象总是被接受
D 归还时校验对象是否真正借出,可防止重复归还 ✓ 正确答案