返回资源中心

Java 并发与多线程面试题精选

技术文章Java 并发与多线程真实面试题:线程池、synchronized 锁升级、volatile、CAS、AQS、ConcurrentHashMap、ThreadLocal、死锁等,每题含标准答案、拓展与项目应用,标注适用年限。2026年6月23日8 次阅读

Java 后端真实面试专题 · 并发与多线程篇

并发是 10–25k 后端岗的必考区,问得深、追问狠。每题三段: ① 标准答(讲透:是什么+为什么+怎么做)→ ② 拓展(成体系带出关联点和面试官会追问的,答一题等于答一片)→ ③ 怎么接到你自己的项目(背八股的人答不出这段)。

年限标签:🟢 3年内 🔴 3年+ 这一篇的答法本身就是教学:别人问一个点,你成体系地答一片——这才是面试官眼里的"懂"。


1. 🟢 线程有哪几种状态?是怎么流转的?

标准答:Java 线程有 6 种状态(Thread.State 枚举):

  • NEW:创建了还没 start()
  • RUNNABLE:调了 start(),包含"就绪"和"运行中"——Java 没区分这两者,因为是否真正占用 CPU 由操作系统调度,JVM 层看不到。
  • BLOCKED:等待进入 synchronized 同步块、抢锁失败被阻塞。
  • WAITING:调了 wait()/join()/LockSupport.park(),无限期等,要别人唤醒。
  • TIMED_WAITING:带时间的等待,如 sleep(n)wait(n)join(n)
  • TERMINATED:run 方法执行完或异常退出。

拓展:面试官常顺着追问:

  • "BLOCKED 和 WAITING 区别?"——BLOCKED 是等锁(被动,抢 synchronized 没抢到),WAITING 是主动等通知(调了 wait/park)。
  • "操作系统层面线程有几种状态?"——新建、就绪、运行、阻塞、终止,Java 的 RUNNABLE 对应了就绪+运行两个。
  • "线程状态在哪看?"——jstack 导出线程栈,每个线程都标了状态,线上排查全靠它。
  • 状态不能跳——比如 NEW 不能直接到 RUNNING,必须经 start。

往项目引 ⭐:"理解状态对排查线上很关键。我项目有次接口大面积超时,jstack 一看几十个线程全 BLOCKED 在同一把锁上,立刻判断是锁竞争,定位到一段范围过大的 synchronized,缩小锁粒度后解决——状态不是背的,是排查问题的工具。"


2. 🟢 sleep 和 wait 的区别?

标准答:最本质一句话——sleep 抱着锁睡,wait 放锁等

  • sleep 是 Thread 的静态方法,让当前线程暂停指定时间、不释放持有的锁,到点自动回到就绪状态。
  • wait 是 Object 的方法,调用后当前线程释放该对象的锁、进入对象的等待队列(WAITING),必须由其他线程调同一对象的 notify/notifyAll 才能唤醒。
  • 两者都能响应中断。

拓展:这题能引出一大片,面试官最爱追:

  • "wait/notify 为什么定义在 Object 而不是 Thread?"——锁是绑在任意对象上的,等待/唤醒针对的是"对象监视器(monitor)",所以必须是 Object 的方法。
  • "为什么 wait 必须在 synchronized 里调用?"——调用前必须先持有该对象的 monitor,否则抛 IllegalMonitorStateException
  • "notify 和 notifyAll 区别?"——notify 随机唤醒一个等待线程(可能信号丢失),notifyAll 唤醒全部再重新竞争锁,生产上一般用 notifyAll 更安全。
  • "wait 为什么要用 while 而不是 if 判断条件?"——防止"虚假唤醒",唤醒后要重新检查条件。
  • 进阶对比 LockSupport.park/unpark:不需要持锁、更灵活,AQS 底层用的就是它。

往项目引 ⭐:"我项目里很少直接写 wait/notify,而是用更上层的 BlockingQueue(它内部就是等待-通知机制)。比如订单异步处理做缓冲队列,消费线程 take() 时队列空了自动阻塞、生产者 put 进来自动唤醒,比手写 wait/notify 安全得多。"


3. 🟢 创建线程有几种方式?为什么实际只用线程池?

标准答:四种——① 继承 Thread 重写 run;② 实现 Runnable(推荐,避免单继承限制);③ 实现 Callable + FutureTask(能拿返回值、能抛异常);④ 线程池。实际开发只用线程池,前三种是基础原理。

拓展

  • "为什么不手动 new Thread?"——频繁创建销毁开销大、线程不可复用、数量不可控(并发一高线程暴涨直接 OOM)。阿里开发规约强制要求用线程池。
  • Runnable 和 Callable 区别——Callable 有返回值 call()、能抛受检异常,配合 Future 拿结果。
  • Future 的问题——get() 会阻塞,所以 JDK8 出了 CompletableFuture 做异步编排。
  • 本质上四种方式的"任务"和"执行"是分离的,线程池就是把"执行"复用起来。

往项目引 ⭐:"我项目所有异步任务都走自定义线程池。比如商品批量导入,用 Callable 把每一批丢进线程池并行处理、再用 Future 收集结果,十万条从几分钟降到几十秒——既复用线程又能拿到每批的处理结果。"


4. 🟢 并发编程的三大特性是什么?

标准答

  • 原子性:一个或多个操作要么全做完、要么都不做,中间不被打断。靠 synchronized、Lock、Atomic 类保证。
  • 可见性:一个线程改了共享变量,其他线程能立刻看到。靠 volatile、synchronized、final 保证(每个线程有自己的工作内存,改了不刷主存别人看不到)。
  • 有序性:程序执行顺序按代码先后(实际 CPU/编译器会指令重排)。靠 volatile(内存屏障)、synchronized 和 happens-before 规则保证。

拓展

  • "i++ 为什么线程不安全?"——它是"读-改-写"三步,不满足原子性,多线程会丢更新。
  • "volatile 能保证原子性吗?"——不能,只保证可见性和有序性,所以计数要用 AtomicLong
  • "什么是指令重排?为什么允许?"——为了优化性能,单线程下重排不影响结果(as-if-serial),但多线程下会出问题。
  • happens-before 是判断"是否存在数据竞争"的核心规则,比如解锁 happens-before 后续加锁。
  • 三大特性是并发所有问题的根,几乎所有并发工具都是在解决这三个中的某一个。

往项目引 ⭐:"我项目里按'缺哪个特性补哪个'来选工具:优雅停机的状态开关只要可见性,用 volatile;并发计数要原子性,用 AtomicLong;复合操作要原子性+互斥,才上 synchronized/Lock。而不是无脑一把锁锁到底,那样性能差。"


5. 🟢 线程池的核心参数有哪些?一个任务提交进来的完整流程?

标准答ThreadPoolExecutor 七个参数——核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、空闲线程存活时间(keepAliveTime)、时间单位、阻塞队列(workQueue)、线程工厂(threadFactory)、拒绝策略(handler)。 提交一个任务的流程:① 核心线程没满 → 创建核心线程执行;② 核心线程满了 → 进阻塞队列排队;③ 队列也满了 → 创建非核心线程(直到最大线程数);④ 达到最大线程数且队列满 → 触发拒绝策略。

拓展

  • 高频追问"先创建线程还是先入队?"——先入队、再扩线程,很多人答反。原因是入队比创建线程代价小。
  • "keepAliveTime 对核心线程生效吗?"——默认不,除非设 allowCoreThreadTimeOut(true)
  • 阻塞队列怎么选——有界队列(ArrayBlockingQueue/有界 LinkedBlockingQueue)防止任务无限堆积 OOM;SynchronousQueue 不存任务直接交付。
  • 这套"先核心→再队列→再扩容→再拒绝"的设计哲学:优先复用、其次缓冲、最后才扩张和拒绝。

往项目引 ⭐:"我项目按业务隔离了多个线程池——订单、消息推送各用各的,避免一个业务把线程占满拖垮另一个(线程池隔离)。核心数是压测后定的,队列用有界队列,宁可触发拒绝策略也不让任务无限堆积把内存打爆。"


6. 🟢 线程池的拒绝策略有哪些?生产上你用哪种?

标准答:JDK 内置四种——

  • AbortPolicy(默认):直接抛 RejectedExecutionException
  • CallerRunsPolicy:让提交任务的线程自己执行该任务(相当于"反压",降低提交速度)。
  • DiscardPolicy:默默丢弃新任务,不报错。
  • DiscardOldestPolicy:丢掉队列里最老的,再尝试提交。

拓展

  • 生产一般不用默认的——抛异常会丢任务且影响主流程。
  • CallerRunsPolicy 适合"不能丢任务、宁可慢"的场景,用调用线程执行天然限流。
  • 大多数情况会自定义拒绝策略:记日志、报警、把任务落库或丢 MQ 后续补偿。
  • 拒绝策略触发说明池子已经扛不住了,要顺带排查是不是参数设小了或下游慢了。

往项目引 ⭐:"我项目自定义了拒绝策略:任务被拒时先记日志报警,再把任务持久化到 DB / 丢进 MQ,等池子空闲了补偿执行,保证重要任务(如订单后续处理)不丢——而不是默认抛异常把任务弄丢了。"


7. 🔴 为什么不建议用 Executors 直接创建线程池?

标准答:阿里规约明确禁止,因为 Executors 的工厂方法藏了 OOM 风险:

  • newFixedThreadPoolnewSingleThreadExecutor 用的是无界队列 LinkedBlockingQueue,任务堆积会撑爆内存。
  • newCachedThreadPoolnewScheduledThreadPool最大线程数是 Integer.MAX_VALUE,线程能无限创建,也会 OOM。 所以要用 new ThreadPoolExecutor(...) 手动指定有界队列和合理的最大线程数。

拓展

  • 这题本质考"你是不是真的踩过/懂线程池参数",背过的人才知道。
  • 延伸到"队列怎么选"——核心是有界,给一个能接受的堆积上限。
  • 再延伸"线程数怎么定"(见下一题)。

往项目引 ⭐:"我项目所有线程池都是 new ThreadPoolExecutor 手动建、统一封装成工具类,用有界队列 + 自定义拒绝策略 + 有意义的线程名(方便 jstack 排查)。就是因为知道 Executors 的无界队列在流量高峰会把内存打爆。"


8. 🔴 线程池的线程数怎么设置?

标准答:看任务类型——

  • CPU 密集型(大量计算):设 CPU 核数 + 1,线程太多只会增加上下文切换开销。
  • IO 密集型(大量读写库/网络):线程多数时间在等 IO,可设 核数 * 2 甚至更高。
  • 更精确的经验公式:线程数 = 核数 × (1 + IO 耗时 / CPU 耗时)
  • 最终都要压测,按吞吐和响应时间调到最优,公式只是起点。

拓展

  • "为什么 CPU 密集不能开太多线程?"——核数就那么多,线程多了只是轮流切换,切换本身耗 CPU。
  • "怎么判断是 CPU 还是 IO 密集?"——看任务在算还是在等;可用监控看 CPU 利用率。
  • 实际业务大多是 IO 密集(查库、调接口)。
  • 还可以做动态线程池(如美团 DynamicTp),运行时调参数不重启。

往项目引 ⭐:"我项目导入任务是典型 IO 密集(大量读写库),线程数设得比核数高很多,再根据压测的吞吐曲线微调;而做图片处理那种 CPU 密集的池子就设核数附近,避免无谓切换。"


9. 🟢 synchronized 和 ReentrantLock 的区别?

标准答

synchronizedReentrantLock
本质JVM 关键字,自动加解锁JUC 的 API,手动 lock/unlock
释放自动(出块/异常)必须 finally 里 unlock,否则死锁
中断不可中断可中断(lockInterruptibly)
公平只能非公平可选公平/非公平
条件一个等待队列可绑定多个 Condition,精准唤醒
尝试不支持tryLock 可带超时
两者都是可重入锁。

拓展

  • synchronized 的锁升级(无锁→偏向锁→轻量级锁→重量级锁,见下题)让它 JDK6 后已经不慢,简单同步优先用它,代码也更简洁不易出错。
  • ReentrantLock 的优势场景:需要 tryLock 超时、需要可中断、需要多个 Condition(如阻塞队列的"非空"和"非满"两个条件)。
  • "可重入"是什么——同一线程能重复获取自己已持有的锁,避免自己把自己锁死。
  • 读多写少还可以用 ReentrantReadWriteLock 或 StampedLock 提升并发。

往项目引 ⭐:"我项目里大多数同步用 synchronized 就够、简洁可靠;只有一个抢占式任务调度的场景用了 ReentrantLock 的 tryLock(超时)——抢不到锁的线程不能一直死等,超时就放弃去干别的,这是 synchronized 给不了的。"


10. 🔴 synchronized 的锁升级过程?锁信息存在哪?

标准答:为了减少加锁开销,synchronized 的锁会随竞争加剧逐步升级(只能升不能降):

  • 无锁偏向锁:只有一个线程访问,在对象头 Mark Word 里记下该线程 id,下次进入无需 CAS。
  • 偏向锁轻量级锁:出现第二个线程竞争,用 CAS 自旋尝试获取,适合竞争不激烈、锁持有时间短。
  • 轻量级锁重量级锁:自旋超过阈值/竞争激烈,膨胀为重量级锁,靠操作系统互斥量(monitor),抢不到的线程真正阻塞挂起。 锁状态存在对象头的 Mark Word 里。

拓展

  • "为什么要锁升级?"——大多数情况锁竞争不激烈,重量级锁的阻塞/唤醒要切到内核态、很贵,所以先用轻量级方案。
  • 偏向锁在高并发反复竞争下有撤销开销,JDK15 后默认禁用了偏向锁。
  • 自旋是"忙等",消耗 CPU 但避免线程切换,适合锁很快释放的场景。
  • 引申到对象内存布局:对象头(Mark Word + 类型指针)+ 实例数据 + 对齐填充。

往项目引 ⭐:"理解锁升级让我对 synchronized 有底——它在低竞争下其实很轻量。所以项目里简单的同步我放心用 synchronized,不会一上来就换成 Lock '显得高级',反而把代码搞复杂。"


11. 🟢 volatile 的作用和底层原理?能保证原子性吗?

标准答:volatile 保证两点——

  • 可见性:写 volatile 变量会立刻刷回主存,读会从主存读最新值。
  • 有序性:通过插入内存屏障禁止指令重排。 不保证原子性(如 volatile int i; i++ 仍线程不安全)。底层靠 CPU 的 lock 前缀指令 + 内存屏障实现。

拓展

  • "经典应用?"——双重检查锁(DCL)单例的 instance 必须加 volatile,否则可能因为"new 对象不是原子的(分配内存→初始化→赋引用,可能重排)"而拿到半初始化对象。
  • "和 synchronized 比?"——volatile 只保证可见性/有序、更轻量、不阻塞;synchronized 还保证原子性、会互斥。
  • 适用场景:一写多读的状态标志位。
  • 要原子复合操作就上 Atomic 或锁。

往项目引 ⭐:"我项目优雅停机用 volatile boolean running 做标志位——主线程改成 false,各工作线程下一轮循环立刻可见并退出,不用加锁。但需要并发累加的计数我一律用 AtomicLong,因为 volatile 保证不了 ++ 的原子性。"


12. 🔴 CAS 是什么?有哪些问题?怎么解决?

标准答:CAS(Compare-And-Swap)是一种乐观的无锁并发手段:比较内存中的值和预期值,相等才把它更新为新值,整个过程由 CPU 指令保证原子。它是 JUC 里 Atomic 类、AQS 的底层基石。 三个问题:

  • ABA:值从 A 改成 B 又改回 A,CAS 看不出变过。解决:加版本号,用 AtomicStampedReference
  • 自旋开销:一直 CAS 失败就一直循环,空耗 CPU。
  • 只能保证一个变量的原子操作。解决:把多个变量包成一个对象,用 AtomicReference

拓展

  • "CAS 和加锁比好在哪?"——无锁、不阻塞线程、没有线程切换开销,适合竞争不激烈的场景。
  • Atomic 类底层就是"CAS + 自旋"(getAndIncrement 循环 CAS 直到成功)。
  • 竞争激烈时 CAS 自旋失败率高,JDK8 的 LongAdder 用分段(Cell)思想分散竞争,比 AtomicLong 更适合高并发计数。

往项目引 ⭐:"我项目的库存扣减本质就是 CAS 思想——update stock set stock=stock-1 where id=? and stock>0,这是数据库层的乐观锁:不加悲观锁、靠条件更新,影响行数为 0 就说明卖光了。既防了超卖又避免了悲观锁的性能损耗。"


13. 🔴 AQS 的原理是什么?

标准答:AQS(AbstractQueuedSynchronizer)是 ReentrantLock、CountDownLatch、Semaphore、ReentrantReadWriteLock 的共同底层。核心两部分:

  • 一个 volatile 的 state:表示同步状态(如锁的重入次数、信号量的剩余许可)。
  • 一个 CLH 变体的双向队列:抢不到资源的线程包装成 Node 入队、阻塞等待。 线程通过 CAS 改 state 来争夺资源,成功则执行、失败则入队挂起(LockSupport.park),释放时唤醒队首。

拓展

  • 两种模式:独占(ReentrantLock,一次一个线程拿 state)、共享(CountDownLatch/Semaphore,多个线程可同时拿)。
  • 公平锁 vs 非公平锁:公平锁严格按队列顺序、非公平锁允许新来的线程直接抢(吞吐更高,ReentrantLock 默认非公平)。
  • AQS 用了模板方法模式,子类只需实现 tryAcquire/tryRelease。
  • 底层挂起/唤醒用 LockSupport.park/unpark(不需要持锁,比 wait/notify 灵活)。

往项目引 ⭐:"这题偏底层,我面试会这么答:'我用过基于 AQS 的工具——ReentrantLock 做互斥、CountDownLatch 等多个并行任务完成、Semaphore 做并发数限流,并理解它们底层都是 state + 等待队列',把理论稳稳接到我真实用过的类上,而不是空背源码。"


14. 🟢 ConcurrentHashMap 是怎么保证线程安全的?和 HashMap、Hashtable 的区别?

标准答:JDK8 的 ConcurrentHashMap 用 数组 + 链表/红黑树,put 时:桶为空用 CAS 放入;桶非空对该桶的头节点 synchronized 加锁,只锁单个桶。所以并发度高(理论上等于桶数量)。

  • HashMap:线程不安全,并发 put 会丢数据、JDK7 还会扩容成环。
  • Hashtable:用 synchronized 锁整个表,安全但性能差。
  • ConcurrentHashMap:锁桶粒度,安全且高效。

拓展

  • "JDK7 和 8 的实现差异?"——JDK7 用分段锁(Segment,默认 16 段),JDK8 改成 CAS + synchronized 锁单桶,粒度更细。
  • "size() 怎么算的?"——用 baseCount + CounterCell 数组分散统计,避免单点竞争。
  • "key/value 能为 null 吗?"——不能,因为并发下 null 无法区分"不存在"还是"值为 null"。
  • 扩容时支持多线程协助迁移(transfer),提升扩容速度。

往项目引 ⭐:"我项目里本地缓存、并发计数这些都用 ConcurrentHashMap,而不是 Collections.synchronizedMap(那是锁整个 map、性能差)。比如统计各接口调用量,用 ConcurrentHashMap<String, LongAdder>,高并发下也准也快。"


15. 🟢 乐观锁和悲观锁的区别?分别用在什么场景?

标准答

  • 悲观锁:假设并发一定有冲突,操作前先加锁、独占资源。如 synchronized、select ... for update
  • 乐观锁:假设冲突很少,不加锁直接操作,更新时校验有没有被别人改过。如版本号机制、CAS。 选择:读多写少、冲突概率低用乐观锁(少了加锁开销);写多、冲突激烈用悲观锁(乐观锁会大量重试反而更差)。

拓展

  • 乐观锁实现版本号:表加 version 字段,更新时 where version=旧值,成功才说明没被改、并把 version+1。
  • CAS 也是乐观锁思想。
  • 悲观锁要注意锁范围和死锁。
  • 数据库乐观锁失败后业务上要决定是重试还是报错给用户。

往项目引 ⭐:"我项目库存扣减用乐观锁(stock>0 条件更新),并发高、冲突可接受、失败就提示'已抢完';而账户转账这种要绝对一致的,用 select for update 悲观锁锁住两个账户,避免中间被改。"


16. 🟢 CountDownLatch、CyclicBarrier、Semaphore 的区别?

标准答

  • CountDownLatch(倒计数门闩):让一个/多个线程等待另一组线程完成。countDown() 减一、await() 等到归零。一次性,不能重用。
  • CyclicBarrier(循环栅栏):让一组线程互相等待,都到齐了再一起继续。可重复使用,还能设到齐后执行的回调。
  • Semaphore(信号量):控制同时访问某资源的线程数,acquire() 拿许可、release() 还,常用于限流。

拓展

  • CountDownLatch 是"一个等多个"或"多个等一个开始",CyclicBarrier 是"多个互相等齐"。
  • CountDownLatch 计数到 0 不能复位,要重复用得换 CyclicBarrier。
  • 三者底层都是 AQS(前两个共享模式,Semaphore 控制 state 为许可数)。

往项目引 ⭐:"我项目首页要并行查商品、库存、营销三个服务,全部回来再聚合返回,就用 CountDownLatch(3),三个查询各 countDown 一次、主线程 await 等齐;接口并发保护用 Semaphore 限制同时处理的请求数。"


17. 🔴 CompletableFuture 怎么用?解决了什么问题?

标准答:CompletableFuture 是 JDK8 的异步编排工具,解决了 Future get() 阻塞、无法编排依赖、回调地狱的问题。常用:

  • supplyAsync 异步执行有返回值的任务;
  • thenApply/thenAccept 对结果做转换;
  • thenCompose 串联有依赖的两个异步任务;
  • thenCombine 合并两个独立任务的结果;
  • allOf/anyOf 等待全部/任一完成。

拓展

  • "要不要指定线程池?"——必须传自定义线程池,否则默认用 ForkJoinPool 公共池,被一个慢任务占满会影响全局。
  • 异常处理用 exceptionally/handle,别让异步异常被吞掉。
  • 和 Future 比:Future 只能阻塞 get 或轮询,CompletableFuture 能声明式编排。

往项目引 ⭐:"我项目首页聚合接口原来串行调三个服务要 2 秒,改成 CompletableFuture 用自定义线程池并行发起、thenCombine 汇总,整体耗时变成最慢的那个服务(几百毫秒)。这是'多接口并行聚合'的标准做法,面试官很爱听。"


18. 🔴 怎么保证多个线程的执行结果是有序的?

标准答:线程调度本身不保证顺序,要"有序"得靠编排:

  • 结果有序:给每个任务带序号,并行执行、最后按序号重排结果。
  • 执行有序(严格串行):用单线程池,或用 CompletableFuture 的 thenCompose 串起来。
  • 等齐再走:CountDownLatch / CyclicBarrier。 别指望"按提交顺序进线程池就会按顺序执行"。

拓展

  • "需要顺序消费消息怎么办?"——把同一类消息发到同一队列/分区,单线程消费(引到 MQ 顺序消费)。
  • 并行 + 有序往往是"并行计算、串行汇总"。

往项目引 ⭐:"我项目批量处理要并行提速、但结果必须按原顺序返回给前端,我给每个任务带上索引并行跑,最后用索引把结果重新排好,既拿到了并行的速度又保住了顺序。"


19. 🟢 ThreadLocal 是什么?底层原理?有什么坑?

标准答:ThreadLocal 给每个线程一份独立变量副本,实现线程内的数据隔离与传递。底层:每个 Thread 对象里有一个 ThreadLocalMap,key 是 ThreadLocal 对象(弱引用)、value 是值。get/set 操作的是当前线程自己的这个 map。

拓展

  • "为什么会内存泄漏?"——key 是弱引用会被 GC 回收,但 value 是强引用还挂在 map 上,线程池线程长期复用就会堆积。所以用完一定 remove()(最好放 finally)。
  • "key 为什么用弱引用?"——为了 ThreadLocal 对象本身能被回收,是一种缓解措施,但不能替代 remove。
  • "父子线程怎么传值?"——InheritableThreadLocal,但线程池场景下要用阿里的 TransmittableThreadLocal(TTL)。

往项目引 ⭐:"我项目用 ThreadLocal 存当前登录用户和租户 id——请求进来在拦截器里 set、整条调用链都能取到、请求结束在 finally 里 remove 防泄漏。多租户 SaaS 这套几乎是标配,也是它最典型的真实用法。"


20. 🔴 死锁是怎么产生的?怎么定位和避免?

标准答:死锁是两个以上线程互相持有对方需要的锁、谁也不放,永久等待。产生需同时满足四个条件——互斥、持有并等待、不可剥夺、循环等待。破坏任意一个即可避免,工程上最常用是破坏"循环等待":统一加锁顺序

拓展

  • "怎么定位死锁?"——jstack 会直接打印 Found one Java-level deadlock 和涉及的线程与锁;也能用 jconsole/Arthas 检测。
  • 其他避免手段:用 tryLock(超时) 拿不到就放弃(破坏"不可剥夺/持有并等待");减少锁持有时间和范围;尽量用无锁结构。
  • 死锁、活锁、饥饿的区别——活锁是不断重试但都让步谁也不前进,饥饿是某线程一直抢不到资源。

往项目引 ⭐:"我项目转账场景两个账户互转曾经死锁——A 转 B 锁了 A 等 B,B 转 A 锁了 B 等 A。后来统一规则'按账户 id 从小到大依次加锁',破坏了循环等待,问题根治。这是死锁避免最经典也最实用的手段。"


你能答到第几层?

  • 三段都能答、还能往项目引:你是面试里的少数派,冲 18k+ 没问题。
  • 标准答 + 拓展能成体系答:知识扎实,差把它绑到你真做过的项目上。
  • 标准答都磕巴:别急,并发是有主线的(三大特性 → 锁 → JUC 工具 → 线程池),按主线学一遍就通。

这是面试专题的「并发篇」,网站上还有 MySQL、Redis、Spring、微服务、项目场景等系统整理。 🌐 更多真实面试专题与资料:smallredtech.com 💬 想系统学 / 简历与辅导咨询,加微信:Ahongbb666(备注「面试题」)