一、面试
1、为什么要用线程池?
✅什么是线程池,如何实现的?
线程池通过复用已创建的线程,避免了频繁创建和销毁线程的开销(如内存分配、JVM调度),从而提升系统性能。它能有效控制并发线程数量,防止资源耗尽,同时提供任务队列和拒绝策略,增强系统的稳定性和响应速度。此外,线程池简化了线程管理,支持定时、周期性任务执行,适用于高并发场景。
2、Java 里还有哪些使用了池化思想的技术?
Java 中池化技术的典型应用包括:
数据库连接池(如 HikariCP、Druid):复用数据库连接,减少连接创建开销。
对象池(如 Apache Commons Pool):缓存对象实例,避免重复初始化。
HTTP 连接池(如 Apache HttpClient):复用 HTTP 连接,提升请求效率。
线程池(如 ExecutorService):复用线程资源。
内存池(如 Netty 的 PooledByteBufAllocator):减少内存碎片和分配耗时。
3、Java 提供了哪几种线程池?
Java 通过 Executors 工厂类提供了四种线程池:
FixedThreadPool:固定大小的线程池,使用无界队列,适用于负载稳定的场景。
CachedThreadPool:可缓存的线程池,适合短时异步任务,线程数按需增长。
ScheduledThreadPool:支持定时及周期性任务执行。
SingleThreadExecutor:单线程顺序执行,保证任务无并发问题。
此外,ForkJoinPool 用于分治任务(如递归分解任务),WorkStealingPool(JDK 8+)基于工作窃取算法提升并行效率。
4、它们的使用场景、区别及选择原则是什么?
FixedThreadPool:适合已知并发量且任务稳定的场景(如 Web 服务器请求处理)。
CachedThreadPool:适合执行大量短期异步任务(如批量数据处理),但需警惕无界队列导致 OOM。
ScheduledThreadPool:用于定时任务调度(如日志清理、心跳检测)。
SingleThreadExecutor:需保证顺序执行且无并发问题的场景(如事件回调)。
选择原则:根据任务类型(CPU/IO密集)、并发量、资源限制及任务特性(是否需顺序执行)综合评估。
5、线程池的核心参数有哪些?
corePoolSize:核心线程数,即使空闲也保留。
maximumPoolSize:最大线程数,任务队列满时扩容的极限。
keepAliveTime:非核心线程的空闲存活时间。
workQueue:任务队列(如 LinkedBlockingQueue、SynchronousQueue)。
threadFactory:线程创建工厂,可自定义线程名称、优先级等。
RejectedExecutionHandler:拒绝策略(如抛出异常、丢弃任务)。
6、如何根据 CPU 密集型和 IO 密集型任务设置参数?
CPU 密集型:核心线程数 = CPU 核心数 + 1(避免线程切换开销),队列长度适中。
IO 密集型:核心线程数 = CPU 核心数 × 2(因线程常阻塞等待 IO),队列可适当增大或使用有界队列。
实际需结合系统负载测试调整,例如通过 Runtime.getRuntime().availableProcessors() 动态获取 CPU 核心数。
7、队列长度设置的依据是什么?
队列长度需权衡内存资源和任务处理效率:
有界队列(如 ArrayBlockingQueue):防止内存溢出,但需配置合理的拒绝策略。
无界队列(如 LinkedBlockingQueue):适合突发流量,但可能导致任务堆积。
依据包括:系统可用内存、任务到达速率、任务处理时间、队列类型(如优先级队列需固定长度)。通常通过压力测试确定最佳值。
8、动态调整线程池组件时,超出部分的线程怎么办?
会调用锁,获得到锁后才会中断线程。
9、如何快速定位线程泄露问题?
线程转储分析:通过 jstack 或 VisualVM 生成线程快照,检查线程状态(如 BLOCKED 或 WAITING)及堆栈跟踪。
监控工具:使用 Prometheus + Grafana 监控线程数变化,或 Arthas 动态跟踪线程创建。
代码审查:检查线程池配置(如是否误用 Thread.sleep 或未释放资源)。
内存分析:MAT 工具分析 Thread 对象引用链,定位未回收的线程。
10、使用 Redis 实现动态调整的原因是什么?是否有替代方案?
Redis 支持原子操作和高性能读写,适合实时更新配置(如通过 SET 命令动态修改参数)。替代方案包括:
ZooKeeper/Etcd:通过监听机制实现配置同步。
数据库:存储配置表,定时轮询更新。
本地缓存:结合 Guava Cache 或 Caffeine,但需处理一致性。
Redis 的临时节点(如结合 Sentinel)可用于故障转移,但更常见的动态调整是通过外部配置中心实现。
11、AQS 锁的实现原理是什么?
✅如何理解AQS?
AQS(AbstractQueuedSynchronizer)基于 CLH 队列的变体,使用一个 volatile int state 表示同步状态,线程通过 CAS 竞争修改状态。
独占模式(如 ReentrantLock):线程尝试获取锁,失败则进入等待队列,释放时唤醒队首。
共享模式(如 Semaphore):允许多个线程同时获取锁。
AQS 内部维护双向链表(CLH 队列),通过 Node 对象管理等待线程,结合自旋和阻塞减少 CPU 消耗。
12、ThreadLocal 为什么用 ThreadLocalMap 而不是 HashMap?
线程隔离:每个线程持有独立的 ThreadLocalMap,避免多线程竞争,无需同步开销。
内存泄漏预防:ThreadLocalMap 的 Entry 继承 WeakReference,当 ThreadLocal 实例被回收时,Entry 的键自动清除,防止内存泄漏(但值仍需手动清理)。
性能优化:直接访问线程内部的 Map,减少哈希冲突和锁竞争。
13、Runnable 和 Callable接口的区别是什么?
返回值:Runnable 的 run() 无返回值,Callable 的 call() 返回泛型结果。
异常处理:Callable 允许抛出受检异常(checked exception),Runnable 的异常需在内部捕获。
用途:Runnable 用于简单异步任务,Callable 适用于需返回结果或抛出异常的场景(如 FutureTask)。
14、数据库分片有哪些方式?水平拆分的缺点是什么?
分片方式:
垂直分片:按业务拆分表(如订单库、用户库)。
水平分片:按规则(哈希、范围)拆分同一表的数据。
水平拆缺点:
跨分片查询复杂(需聚合或冗余字段)。
数据分布不均可能导致热点问题。
扩容时需数据迁移,影响可用性。
15、SQL如何调优?
✅SQL执行计划分析的时候,要关注哪些信息?
16、数据库如何解决主从延迟问题?
✅什么是数据库的主从延迟,如何解决?
✅MySQL的并行复制原理
并行复制:从库多线程应用 relay log。
半同步复制:主库等待至少一个从库确认写入。
应用层优化:读写分离时,强一致性场景直接读主库,最终一致性场景异步读从库。
延迟监控:通过 SHOW SLAVE STATUS 监控 Seconds_Behind_Master,及时报警。
17、Redis 缓存穿透、雪崩、击穿的防御手段是什么?
✅什么是缓存击穿、缓存穿透、缓存雪崩?
穿透:布隆过滤器拦截非法请求,或缓存空值(设置较短 TTL)。
雪崩:随机过期时间,集群部署,永不过期+异步更新。
击穿:互斥锁(如 Redis 的 SETNX)或永不过期,后台线程定时刷新。
18、Redis 集群如何避免脑裂问题?选举临时节点的机制是什么?
脑裂预防:多数派原则(Quorum),配置 cluster-require-full-coverage no 避免部分节点宕机导致整体不可用。
临时节点:通过 ZooKeeper/Etcd 等协调服务创建临时节点,节点消失时自动触发选举。
选举机制:基于 Raft 或类似算法,节点通过心跳检测故障,多数派确认新主节点。
19、平时常用的设计模式有哪些?
单例模式(Spring Bean 默认作用域)。
工厂模式(JDBC 驱动、Hibernate SessionFactory)。
代理模式(AOP、动态代理)。
观察者模式(Spring 事件监听)。
策略模式(多支付渠道切换)。
装饰器模式(Java I/O 流)。
模板方法模式(Spring JdbcTemplate)。
20、Spring 框架中用到了哪些设计模式?
模板方法:JdbcTemplate、RestTemplate 封装公共流程。
工厂模式:BeanFactory 创建 Bean 实例。
代理模式:JDK 动态代理或 CGLIB 实现 AOP。
单例模式:默认 Bean 作用域。
观察者模式:ApplicationEvent 和 ApplicationListener。
适配器模式:HandlerAdapter 适配不同处理器。
装饰器模式:BeanPostProcessor 增强 Bean 初始化过程。