第一轮:Java基础与多线程
面试官:请介绍一下Java中线程池的核心参数。
谢飞机:呃……线程池?是不是就是new Thread()然后.start()?
面试官:……我们说的是ThreadPoolExecutor。
谢飞机:哦!那个啊,我知道!有核心线程数、最大线程数、队列、拒绝策略!
面试官(点头):不错,那这些参数分别有什么作用?
谢飞机:核心线程数是常驻线程,最大线程数是最多能开多少线程,队列用来放任务,拒绝策略是干不完了就拒绝!
面试官:还行。那如果队列满了,会发生什么?
谢飞机:那就新开线程,直到达到最大线程数,再不行就触发拒绝策略,比如抛异常或者丢弃任务。
面试官:很好。
第二轮:JUC与并发容器
面试官:ConcurrentHashMap 是如何实现线程安全的?
谢飞机:它是分段锁,每个段独立加锁,这样多个线程可以同时操作不同的段。
面试官:JDK 1.8 呢?
谢飞机:呃……还是分段锁吧?
面试官:不对,1.8 改为CAS + synchronized了。
谢飞机:啊?那我记混了……
面试官:那你来说说 volatile 关键字的作用?
谢飞机:保证可见性!一个线程修改了,其他线程立刻能看到!
面试官:还有呢?
谢飞机:还能防止指令重排序!
面试官:不错。
第三轮:Spring 与分布式技术
面试官:Spring Bean 的作用域有哪些?
谢飞机:singleton、prototype,还有request、session……
面试官:很好。那 singleton 是线程安全的吗?
谢飞机:是!因为只有一个实例!
面试官:如果这个 Bean 里有成员变量呢?
谢飞机:呃……那可能就不安全了?
面试官:对。那 Redis 做分布式锁要注意什么?
谢飞机:用 SETNX!还要设置过期时间!不然死锁!
面试官:如果业务执行时间超过过期时间呢?
谢飞机:呃……那我就续命!Redisson 有看门狗!
面试官:还行。最后,xxl-job 如何保证任务不重复执行?
谢飞机:它有个调度中心,通过数据库锁来控制……吧?
面试官:嗯,差不多。今天先到这里,你回去等通知吧。
谢飞机:好的好的,谢谢面试官!
答案详解
1. ThreadPoolExecutor 核心参数
- corePoolSize:核心线程数,即使空闲也不会被回收(除非设置 allowCoreThreadTimeOut)。
- maximumPoolSize:最大线程数,当队列满时,线程池会创建新线程直到达到此值。
- workQueue:任务队列,如
LinkedBlockingQueue、ArrayBlockingQueue。 - keepAliveTime:非核心线程空闲存活时间。
- unit:存活时间单位。
- threadFactory:线程工厂,用于创建线程。
- handler:拒绝策略,如
AbortPolicy(抛异常)、CallerRunsPolicy(调用者线程执行)等。
2. ConcurrentHashMap 实现原理
- JDK 1.7:采用分段锁(Segment),每个 Segment 相当于一个 HashTable,减少锁竞争。
- JDK 1.8:改为 CAS + synchronized,Node 数组加锁,锁粒度更小,性能更高。
3. volatile 关键字
- 可见性:通过内存屏障保证变量修改后立即写回主内存,其他线程读取时从主内存刷新。
- 有序性:禁止指令重排序,确保代码执行顺序与程序顺序一致。
- 不保证原子性:如 i++ 操作仍需 synchronized 或 AtomicInteger。
4. Spring Bean 作用域
- singleton:默认,IOC 容器中唯一实例。
- prototype:每次获取都创建新实例。
- request/session/application:Web 环境下的作用域。
注意:singleton Bean 不是线程安全的,若包含可变成员变量,需自行保证同步。
5. Redis 分布式锁注意事项
- 使用
SET key value NX EX seconds原子操作。 - 设置合理过期时间,避免死锁。
- 使用 Redisson 可实现自动续期(watchdog)。
- 注意锁误删问题,value 应使用唯一标识(如 UUID)。
6. xxl-job 任务去重机制
- 调度中心通过数据库行锁(for update)保证同一任务在同一时间只被一个调度线程执行。
- 执行器集群部署时,通过任务 ID 和调度时间戳进行幂等控制。