# 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 归还时校验对象是否真正借出,可防止重复归还 ✓ 正确答案