第一轮:线程与线程池
面试官:请介绍一下Java中线程池的核心参数。
谢飞机:嗯……线程池?是不是就是把很多线程放在一起,像池子一样?我一般都直接 new Thread(),跑起来挺快的!
面试官:……那你至少知道 Executors 提供的几种常见线程池吧?
谢飞机:哦!这个我知道!有 FixedThreadPool、CachedThreadPool,还有 SingleThreadExecutor,我都背过!
面试官:不错,那你说说它们分别适用于什么场景?
谢飞机:Fixed 是固定数量的,适合任务多但不想开太多线程;Cached 是随用随开,适合短任务;Single 就是一个线程,按顺序执行。我这回答可以加薪了吧?
面试官(点头):基本概念还行。
第二轮:并发容器与锁机制
面试官:如果多个线程同时读写 HashMap 会有什么问题?
谢飞机:会……会吵架!数据乱掉,可能还抛 ConcurrentModificationException。
面试官:那怎么解决?
谢飞机:可以用 synchronized 包一下,或者用 ConcurrentHashMap!它线程安全,性能还好!
面试官:ConcurrentHashMap 在 JDK 1.8 之后是怎么实现线程安全的?
谢飞机:呃……它是……把数组分段了?每段上锁?不对……好像是用了 synchronized 和 CAS?哎呀,反正就是又锁又原子操作,贼牛!
面试官(皱眉):差不多吧……
第三轮:Spring 与分布式组件
面试官:Spring 中 Bean 的作用域有哪些?
谢飞机:singleton 和 prototype 我最熟!一个全局一个每次新建。
面试官:Spring Boot 自动装配的原理?
谢飞机:就是 @EnableAutoConfiguration,它会扫描 META-INF/spring.factories,把配置类加载进来!我知道它用了 SpringFactoriesLoader!
面试官:Redis 在项目中一般用来做什么?
谢飞机:存 session、做缓存、还能当分布式锁!我们公司连用户登录都靠它!
面试官:用 Redis 做分布式锁需要注意什么?
谢飞机:呃……要设过期时间,不然死锁了怎么办?还有……要用 SETNX?不对,现在都用 SET EX PX NX 加唯一值,再用 Lua 脚本释放……哎,细节太多了我记不清了!
面试官(叹气):今天先到这里,你回去等通知吧。
谢飞机(小声嘀咕):等通知=没戏,懂的都懂……
答案详解
1. 线程池核心参数
Java 线程池 ThreadPoolExecutor 有七大核心参数:
- corePoolSize:核心线程数,即使空闲也不会销毁(除非设置 allowCoreThreadTimeOut)
- maximumPoolSize:最大线程数
- keepAliveTime:非核心线程空闲存活时间
- unit:时间单位
- workQueue:任务队列,如 LinkedBlockingQueue、ArrayBlockingQueue
- threadFactory:线程创建工厂
- handler:拒绝策略,如 AbortPolicy、CallerRunsPolicy
Executors 创建的线程池存在风险,例如 FixedThreadPool 使用无界队列可能导致 OOM,因此推荐手动创建 ThreadPoolExecutor。
2. ConcurrentHashMap 实现原理(JDK 1.8)
- 底层采用 Node 数组 + 链表/红黑树
- 使用 synchronized 对链表头节点或红黑树根节点加锁,替代 JDK 1.7 的分段锁
- 插入时使用 CAS + synchronized 保证线程安全
- 扩容时使用 CAS 标记扩容状态,支持多线程协助迁移
优势:锁粒度更细,性能更高,尤其在高并发读场景下表现优异。
3. Redis 分布式锁注意事项
- 使用
SET key unique_value NX EX max_lock_time命令保证原子性 - unique_value 通常为 UUID 或线程ID,防止误删
- 设置合理的超时时间,避免业务未执行完锁已释放
- 释放锁时需用 Lua 脚本确保判断和删除的原子性:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end - 可结合 RedLock 算法提升可靠性,但需权衡复杂性与收益。