多线程是 Java 面试的绝对高地也是划分“能干”和“能用”的分水岭。我会按照核心理论 → JUC 工具 → 线程池 → 锁机制 → 常见面试题的链路用老练的视角给你拆解每个点都配上代码和血泪教训。一、线程基础与核心理论面试必问1. 线程生命周期六种状态NEW → RUNNABLE → BLOCKED → WAITING → TIMED_WAITING → TERMINATEDBLOCKED等待synchronized锁。WAITINGwait()、join()、LockSupport.park()。TIMED_WAITINGsleep(time)、wait(time)、LockSupport.parkNanos()。面试官爱追问sleep和wait区别sleep是 Thread 静态方法不释放锁wait是 Object 方法释放锁必须在synchronized块中调用。2. 创建线程的方式继承Thread重写run()。实现Runnable作为Thread构造参数。实现Callable配合FutureTask获取返回值。老手只推荐线程池而不是直接new Thread。3.volatile的两层语义可见性写立即刷回主存读强制从主存拿。禁止指令重排序通过内存屏障实现。但不保证原子性比如i依然线程不安全。二、锁机制从 synchronized 到 AQS1.synchronized的升级之路偏向锁 → 轻量级锁自旋→ 重量级锁OS 互斥量JVM 自动优化。锁对象不能是String、包装类型等常量对象。底层原理对象头 MarkWord 记录锁状态monitorenter/monitorexit指令。2. Lock 接口与 AQSReentrantLocklocknewReentrantLock();lock.lock();try{// 业务}finally{lock.unlock();}公平/非公平new ReentrantLock(true)按等待队列顺序吞吐量差。可中断lock.lockInterruptibly()可响应中断。条件变量Condition实现分组唤醒替代Object.wait/notify。ConditionnotEmptylock.newCondition();notEmpty.await();notEmpty.signal();3.synchronizedvsLock特性synchronizedLock自动释放是必须 finally unlock可中断否lockInterruptibly公平锁否可设置精准唤醒只能随机Condition 分组性能高版本优化好复杂场景更灵活三、JUC 并发工具用得最多问得最深1.ConcurrentHashMap原理JDK7Segment 分段锁默认 16 个 Segment。JDK8CAS synchronized锁链表头节点红黑树优化。put流程计算 hash → 死循环 CAS 初始化桶 → 桶为空 CAS 写入 → 有数据锁住头节点插入。2.CopyOnWriteArrayList写时复制写操作加ReentrantLock复制新数组。适合读多写极少场景如白名单写多时内存复制开销巨大。3.CountDownLatch/CyclicBarrier/SemaphoreCountDownLatch一次性一个线程等 N 个线程完成。CountDownLatchlatchnewCountDownLatch(3);// 三个线程执行完 latch.countDown();latch.await();CyclicBarrier可重复N 个线程互相等待到齐后一起执行。Semaphore限流控制同时访问的线程数。4.CompletableFuture异步编程王者CompletableFuture.supplyAsync(()-getUser(userId)).thenApply(user-getOrders(user)).exceptionally(e-Collections.emptyList()).thenAccept(orders-process(orders));线程池指定第二个参数传自定义线程池避免都用 ForkJoinPool。组合thenCombine、allOf、anyOf。四、线程池没自己设计过线程池的 Java 开发不是好开发1. 核心参数与执行流程ThreadPoolExecutor(intcorePoolSize,intmaxPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueRunnableworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)流程线程数 core → 创建新线程。线程数 ≥ core → 任务入队列。队列满线程数 max → 创建新线程。队列满线程数 ≥ max → 执行拒绝策略。2. 拒绝策略AbortPolicy抛异常默认。CallerRunsPolicy调用者线程执行。DiscardPolicy静默丢弃。DiscardOldestPolicy丢弃队列最老任务。3. 线程池大小计算公式CPU 密集型CPU核心数 1。IO 密集型CPU核心数 * 2或CPU核心数 / (1 - 阻塞系数)阻塞系数 0.8~0.9。实际要压测4. 禁止直接用 ExecutorsFixedThreadPool和SingleThreadPool使用无界队列可能 OOM。CachedThreadPool最大线程数为 Integer.MAX_VALUE可能 OOM。必须用ThreadPoolExecutor构造并自定义线程工厂给线程命名和拒绝策略。5. 优雅关闭pool.shutdown();// 不再接受新任务等待已提交任务执行完if(!pool.awaitTermination(60,TimeUnit.SECONDS)){pool.shutdownNow();// 尝试中断}五、高频面试题及话术1. 线程间如何通信volatile和synchronized配合wait/notify。JUC 中的LockCondition。CountDownLatch、CyclicBarrier。更高级BlockingQueue生产者消费者。2.ThreadLocal原理及内存泄漏每个线程维护一个ThreadLocalMapkey 是ThreadLocal的弱引用。static class Entry extends WeakReferenceThreadLocal?。弱引用 key 被 GC 后value 无法回收造成泄漏但set/get/remove时会清理 key 为 null 的条目。最佳实践用完必须remove()。3. 死锁的条件和排查互斥、占有且等待、不可抢夺、循环等待。排查jstack pid看线程状态Found one Java-level deadlock。避免按顺序加锁tryLock(timeout)尝试。4. 如何保证多个线程的顺序执行join()在子线程中调用上一个线程的 join。SingleThreadPool。CompletableFuture.thenApplyAsync链式调用。六、老手的性能调优经验锁粒度缩小同步代码块使用ConcurrentHashMap替代Hashtable。锁分离如LinkedBlockingQueue的 put 和 take 使用不同锁。无锁化CAS、AtomicInteger、LongAdder高并发累加。异步化CompletableFuture 自定义线程池提高吞吐。监控用ThreadMXBean查看死锁日志记录线程池队列长度和执行时间。七、面试模板话术当面试官让你“聊聊多线程”你可以这样串“多线程我理解核心是并发安全和执行效率。安全上我掌握 volatile、synchronized 和 Lock 的区别与升级AQS 原理能推导工具方面熟悉 ConcurrentHashMap 的 CASsynchronized 实现用 CountDownLatch 做并行协作CompletableFuture 编排异步任务。线程池一定自己用 ThreadPoolExecutor 构造根据 IO 或 CPU 密集型计算公式设置大小并自定义拒绝策略和命名线程工厂。线上曾通过 jstack 排查过死锁用 ThreadLocal 传参但记得 remove 防止泄漏。这些是我实际生产中的积累。”这样既有理论基础又有踩坑和实战面试官会认为你确实是经验丰富的 Java 老手。还需深入哪个点随时说。在分布式和微服务架构里锁和事务这两样东西直接从单机模式跨到了多节点协同复杂度翻倍。真正老练的 Java 工程师不会死背概念而是能把不同场景下选什么方案、怎么落地、掉过什么坑讲清楚。下面我拆成两部分硬核输出。一、分布式锁1. 为什么需要分布式锁微服务多实例部署JVM 层的synchronized/ReentrantLock只能锁住自己的进程无法阻止另一个实例的线程同时执行。比如订单支付防止重复扣款库存扣减防止超卖定时任务多实例下只跑一次2. 实现方式对比面试凸显视野方案原理优点缺点RedisRedissonSETNX Lua 脚本看门狗续期性能高API 友好极端情况下可能不一致主从切换Zookeeper临时顺序节点 Watcher 机制强一致性自动释放性能差锁释放通知有延迟数据库SELECT ... FOR UPDATE或唯一索引简单性能极差锁表风险生产主流中高并发选 RedisRedisson强一致且并发不高的场景如配置变更选 Zookeeper。3. Redisson 分布式锁的正确逻辑重点AutowiredprivateRedissonClientredissonClient;publicvoidpay(StringorderId){RLocklockredissonClient.getLock(order:pay:orderId);try{// 尝试加锁最多等10秒锁30秒后自动释放看门狗会续期if(lock.tryLock(10,30,TimeUnit.SECONDS)){// 1. 查订单状态OrderorderorderMapper.selectById(orderId);if(order.getStatus()!0)return;// 已支付// 2. 更新订单order.setStatus(1);orderMapper.updateById(order);// 3. 扣库存stockService.deduct(order.getSkuId());}}catch(InterruptedExceptione){Thread.currentThread().interrupt();}finally{// 关键保证只有持有锁的线程才能解锁if(lock.isHeldByCurrentThread()){lock.unlock();}}}老手的核心逻辑要点锁粒度按业务主键如订单 ID加锁不是全局一把大锁。看门狗机制Redisson 默认开启每 10 秒续期 30 秒即使业务执行超过 30 秒也不会丢锁只要 JVM 没挂。不要手写 SETNX EXPIRE非原子容易造成死锁Redisson 底层用 Lua 保证原子性。解锁检查持有者防止锁超时被释放后删了别的线程的锁。4. 分布式锁引发的陷阱与进阶锁超时业务没执行完怎么办看门狗保底或者设计业务幂等真的超时了被释放也能让后续请求因幂等校验不通过而安全返回。主从切换锁丢了怎么办红锁RedLock方案上多台独立 Redis大多数节点加锁成功才算获取但性能开销大大部分公司用哨兵/Cluster 幂等兜底就够了。性能优化锁的读写分离——读操作不加锁或使用读写锁RReadWriteLock。二、分布式事务1. 经典模型概览方案原理适用场景XA 二阶段提交事务管理器协调prepare commit强一致性性能极差TCCTry-Confirm-Cancel业务层面预留、确认或取消资金转账、扣减类性能较好Saga长事务拆成多个本地事务有补偿操作流程长、对一致性要求不太严的订单流程AT 模式Seata自动生成回滚日志代理数据源无侵入类似本地事务编程生产中较广2. Seata AT 模式实战最接近微服务落地Seata 的 AT 模式相当于无侵入的分布式事务中间件业务代码只用加一个GlobalTransactional。ServicepublicclassOrderService{AutowiredprivateOrderMapperorderMapper;AutowiredprivateAccountFeignClientaccountClient;AutowiredprivateStorageFeignClientstorageClient;GlobalTransactional(namecreate-order,rollbackForException.class)publicvoidcreateOrder(OrderDTOdto){// 1. 创建订单本地事务orderMapper.insert(dto);// 2. 远程调用扣减账户余额accountClient.debit(dto.getUserId(),dto.getAmount());// 3. 远程调用扣减库存storageClient.deduct(dto.getSkuId(),dto.getCount());}}背后逻辑一阶段Seata 代理各服务数据源业务 SQL 执行时自动生成回滚日志undo_log和业务 SQL 一起提交到本地数据库。二阶段提交全局事务管理器接到所有分支成功通知后异步删除 undo_log。二阶段回滚任何一个分支失败TM 通知各 RM 根据 undo_log 反向补偿生成反向 SQL 执行还原。老手的注意点undo_log 表必须在每个业务库中创建定期清理。AT 模式隔离级别默认为读未提交要用GlobalLockSELECT FOR UPDATE加强。性能损耗主要在一阶段事务的等待和二阶段的异步处理一般比 TCC 慢一点但开发成本极低。3. TCC 场景代码示意给你思路如果扣钱、扣库存必须强隔离用 TCC 自己写准备、提交、回滚接口。// 账户服务接口publicinterfaceAccountTccService{TransactionalvoidtryDebit(StringuserId,BigDecimalamount);// 冻结资金voidconfirm(StringuserId);// 扣减冻结资金voidcancel(StringuserId);// 解冻资金}// 业务调用方GlobalTransactionalpublicvoidcreateOrder(OrderDTOdto){orderMapper.insert(dto);accountTccService.tryDebit(dto.getUserId(),dto.getAmount());storageTccService.tryDeduct(dto.getSkuId(),dto.getCount());}每个try在本地库预留资源如把余额从可用转冻结如果全局失败cancel退回资源。4. 分布式事务的最终一致性替代方案很多时候我们并不需要强一致的分布式事务用本地消息表 MQ 最终一致性是更可靠的选择下单订单服务创建订单 本地消息表状态待发送然后发送 MQ。消费方库存服务消费消息扣库存成功则确认失败则本地重试或回退。定时任务扫描消息表重发失败消息。老手会这样权衡“如果是涉及资金强一致性且并发不高用 TCC如果是订单和库存这类用 Seata AT 或 MQ 最终一致性如果完全不需要一致纯补偿就行。”面试话术模板“分布式锁我以 Redis Redisson 为主看门狗续期防死锁配合业务幂等解决超时问题红锁了解但没用过。分布式事务我的原则是能不用就不硬上大部分订单库存通过 MQ 保证最终一致对必须强一致的扣减场景我用 Seata AT 模式零侵入或者 TCC 自定义资源预留和确认/取消核心是保证幂等和补偿可执行。无论哪种异常重试和监控告警必须配套。”这样既展现了方案深度又点出了你的工程权衡能力而不是生搬概念。好的这个问题我们要用老练的 Java 工程师视角来回答先讲清楚哪些集合线程安全、哪些不安全然后扩展到在整个 Java 技术栈中如何系统性地规避线程不安全这部分才是面试官最想听的工程思维。一、Java 集合中的线程安全与不安全1. 线程安全的集合自带安全机制集合底层安全机制特点Vector所有方法synchronized古老性能差已弃用Hashtable所有方法synchronized同 Vector不用了Collections.synchronizedList/Map/Set包装器加synchronized代码块锁粒度粗慎用CopyOnWriteArrayList / CopyOnWriteArraySet写时复制 (ReentrantLock 数组复制)读全无锁适合读远多于写ConcurrentHashMapJDK1.8 CAS synchronized锁头节点分段思想多线程协同扩容高并发首选ConcurrentSkipListMap / Set跳表 CAS高并发且有序BlockingQueue 实现ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, DelayQueueReentrantLockCondition生产者消费者利器ConcurrentLinkedQueue / DequeCAS 无锁算法高并发非阻塞队列2. 线程不安全的集合单机无控制ArrayList,LinkedListHashSet,TreeSetHashMap,TreeMap,LinkedHashMapPriorityQueue非线程安全迭代时修改会导致ConcurrentModificationExceptionfail-fast二、在 Java 中系统性地规避线程不安全老手从不指望单一手段而是根据场景组合策略这里我按从底层到架构的顺序讲。1. 语言级不可变对象最彻底的线程安全对象创建后状态不可改变自然线程安全。publicfinalclassImmutableUser{privatefinalStringname;privatefinalintage;// 构造赋值无 setter}// 集合不可变视图ListStringlistCollections.unmodifiableList(originalList);List.of(a,b,c);// Java 9 本身不可变String、Integer、BigDecimal等不可变类天生安全。2. 同步包装器快速补救粒度粗ListStringsyncListCollections.synchronizedList(newArrayList());MapString,StringsyncMapCollections.synchronizedMap(newHashMap());注意迭代时仍需手动synchronized块否则会 fail-fast。3. JUC 并发集合细粒度锁主流方案ConcurrentHashMap替代HashMapCAS synchronized 仅锁桶支持并发扩容读无锁。CopyOnWriteArrayList替代ArrayList写复制整数组读原数组适合配置、白名单等读多写极少场景。BlockingQueue替代手动wait/notify有界队列防止 OOMput/take阻塞唤醒。ConcurrentLinkedQueue非阻塞 CAS吞吐高。案例高并发计数器用ConcurrentHashMapString, LongAdder避免锁。4. 显式锁与同步块灵活控制复杂逻辑专用synchronized代码块 / 方法适合简单互斥。ReentrantLockCondition可中断、超时、公平、分组唤醒。LocklocknewReentrantLock();lock.lock();try{// 临界区}finally{lock.unlock();}5. 原子类与 CAS无锁算法超高并发AtomicInteger,AtomicLong,AtomicReference基于 CAS自旋更新。LongAdder高并发累加性能优于AtomicLong内部用 Cell 数组分散热点。LongAddercounternewLongAdder();counter.increment();适合计数、统计等场景。6. volatile 保证可见性不保证原子性volatilebooleanflagtrue;一个线程修改 flag其他线程立即可见。注意flag仍线程不安全需AtomicInteger或synchronized。7. 线程封闭ThreadLocal、栈封闭数据不共享自然无线程安全问题。ThreadLocal每个线程一份副本适合日期格式化、上下文传递。privatestaticThreadLocalSimpleDateFormatthreadLocalThreadLocal.withInitial(()-newSimpleDateFormat(yyyy-MM-dd));用后必须remove()防止内存泄漏。栈封闭方法内局部变量不被外泄。8. 线程池与异步编排管理线程资源分离任务用ThreadPoolExecutor显式定义有界队列、拒绝策略避免无节制创建线程。CompletableFuture编排异步任务用thenApplyAsync指定线程池避免共享数据竞争。9. 设计层面规避无状态服务、消息传递无状态 BeanSpring 单例 Service 不存储可变状态只依赖局部变量和线程安全参数。消息队列服务间通过 MQ 异步传递数据减少共享内存各自处理自己的数据库事务保证最终一致性。10. 测试与监控IDEA 的FindBugs/SpotBugs静态分析标注ThreadSafe/NotThreadSafe。编写并发测试使用CyclicBarrier同时放行CountDownLatch等待结束检测数据一致性。线上用jstack分析死锁JMX 监控线程池。三、面试王者话术“Java 里线程安全我分几个层面来保证。数据类首选不可变对象集合选ConcurrentHashMap或CopyOnWriteArrayList计数器用LongAdder。复杂同步逻辑用ReentrantLock实现可中断和超时控制配合Condition。读写多写少用读写锁需要有序用ConcurrentSkipListMap。业务中尽量设计无状态 Service必须共享的状态通过ThreadLocal隔离用完清除。线程池全部自定义有界队列 拒绝策略防止 OOM。最后并发 bug 难以重现我会用静态分析 并发测试 监控日志来兜底。”这样回答既展示了集合层面的选择又有语言机制 → 并发包 → 设计思想 → 测试保障的全链路规避思路面试官会觉得你完全掌握了 Java 线程安全的精髓。