第一轮:Java核心与多线程
面试官:谢飞机,你先说说Java中创建线程有几种方式?
谢飞机:两种!继承Thread类和实现Runnable接口。我还知道Callable+Future可以拿到返回值!
面试官:不错,那线程池的几个核心参数能说一下吗?
谢飞机:额……corePoolSize是核心线程数,maximumPoolSize是最大线程数,还有个队列……叫什么来着?LinkedBlockingQueue?
面试官:那keepAliveTime呢?
谢飞机:这个我知道!多余的空闲线程等待新任务的最长时间!超过就销毁。
面试官:很好,那如果队列满了怎么办?
谢飞机:那就……开更多线程?到maximumPoolSize为止。再不行……我就重启服务器!
面试官:……(记录:基础尚可,但理解不深)
第二轮:JUC与并发容器
面试官:HashMap不是线程安全的,你们项目里怎么解决的?
谢飞机:我们用ConcurrentHashMap!听说它分段锁,性能好!
面试官:JDK 1.8之后呢?
谢飞机:呃……还是分段锁吧?
面试官:错了,1.8之后改用synchronized+CAS了。那CountDownLatch和CyclicBarrier的区别呢?
谢飞机:都是等线程执行完……那个,Cyclic可以循环,像操场跑步,跑完一圈再来一圈!
面试官:那CountDownLatch呢?
谢飞机:只能用一次,像倒计时火箭发射,点火后就不能再点了!
面试官:比喻还行,但技术细节欠缺。
第三轮:Spring生态与中间件
面试官:Spring Bean的作用域有哪些?
谢飞机:singleton单例,prototype每次新建,request和session是Web里的。
面试官:Bean的生命周期了解吗?
谢飞机:Spring创建→属性填充→初始化→使用→销毁。我看过源码,refresh()方法特别长!
面试官:那你项目里用RabbitMQ怎么保证消息不丢失?
谢飞机:生产者确认机制、持久化、消费者手动ACK!我都配置了!
面试官:Redis缓存穿透怎么处理?
谢飞机:布隆过滤器!还有缓存空值。
面试官:如果攻击者绕过布隆过滤器呢?
谢飞机:那就……加防火墙?或者让他穿透,反正我下班了!
面试官:……今天的面试就到这里,回去等通知吧。
答案详解
1. 线程池核心参数
- corePoolSize:核心线程数,即使空闲也不会被回收(除非设置allowCoreThreadTimeOut)。
- maximumPoolSize:最大线程数,当队列满时,线程池会创建新线程直到达到此值。
- workQueue:阻塞队列,如ArrayBlockingQueue、LinkedBlockingQueue等。
- keepAliveTime:非核心线程空闲时的存活时间。
- RejectedExecutionHandler:拒绝策略,如AbortPolicy、CallerRunsPolicy等。
2. ConcurrentHashMap JDK 1.8优化
- 放弃Segment分段锁,采用Node数组+synchronized+CAS实现锁粒度更细的同步。
- 在链表长度超过8且数组长度大于64时,链表转为红黑树。
3. CountDownLatch vs CyclicBarrier
- CountDownLatch:一次性,计数器不可重置,主线程等待多个子线程完成。
- CyclicBarrier:可重复使用,所有线程互相等待,到达屏障点后一起继续执行。
4. Spring Bean生命周期
- 实例化(构造函数)
- 属性赋值
- Aware接口回调(如BeanNameAware)
- BeanPostProcessor前置处理
- 初始化方法(@PostConstruct或init-method)
- BeanPostProcessor后置处理
- 使用
- 销毁(@PreDestroy或destroy-method)
5. Redis缓存穿透防御
- 布隆过滤器:快速判断key是否存在,减少对数据库的无效查询。
- 缓存空值:对不存在的key也缓存一个空对象,并设置较短过期时间。
- 接口层增加校验:如用户鉴权、参数合法性检查。
- 限流降级:防止恶意请求压垮系统。