
大厂规范Java项目工具类 — 05_文本文件读取工具类Java企业级代码第18题BLOCKED 和 WAITING 有什么区别回答核心考点BLOCKED和WAITING是 Java 线程状态中最容易混淆的两种状态大厂面试不会只问被动 vs 主动而是深入考察底层 Monitor 对象的队列结构_EntryListvs_WaitSet、锁释放行为的差异不释放 vs 释放 Monitor 锁、以及中断响应的不同不可中断 vs 可中断。面试官真正想判断的是你是否能从 HotSpot 源码层面理解两种状态的本质区别以及能否在生产环境中通过jstack准确区分和排查问题。1. 本质区别——Monitor 对象的双队列模型BLOCKED和WAITING的根本差异在于线程在 Monitor 对象中的位置不同、持有的锁状态不同、唤醒机制不同。1.1 Monitor 对象结构回顾┌─────────────────────────────────────────────────────────┐ │ ObjectMonitor │ ├─────────────────────────────────────────────────────────┤ │ _owner → 持有锁的线程 │ │ _recursions → 重入次数 │ │ _count → 获取锁的次数含重入 │ │ _waiters → 等待线程数 │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ _EntryList │ │ _WaitSet │ │ │ │ (BLOCKED 队列) │ │ (WAITING 队列) │ │ │ │ │ │ │ │ │ │ Thread-B │ │ Thread-C │ │ │ │ Thread-D │ │ Thread-E │ │ │ │ (竞争锁失败) │ │ (调用 wait()) │ │ │ └─────────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────┘1.2 BLOCKED 状态详解维度BLOCKED触发原因竞争synchronized的 Monitor 锁失败所在队列_EntryList或_cxq竞争队列锁状态不持有Monitor 锁唤醒条件锁持有者执行monitorexitJVM 从_EntryList唤醒唤醒方式自动无需其他线程显式操作中断响应不可中断只能等锁释放状态转换RUNNABLE → BLOCKED → RUNNABLE源码级理解// ObjectMonitor::enterOpenJDK 8voidObjectMonitor::enter(TRAPS){Thread*constSelfTHREAD;void*cur;// CAS 尝试获取锁curAtomic::cmpxchg_ptr(Self,_owner,NULL);if(curNULL){// 获取成功直接返回return;}if(curSelf){// 重入_recursions_recursions;return;}// 获取失败进入 _EntryList 或 _cxq状态变为 BLOCKEDEnterI(THREAD);}1.3 WAITING 状态详解维度WAITING触发原因已持有锁的线程调用Object.wait()所在队列_WaitSet条件队列锁状态已释放Monitor 锁唤醒条件其他线程调用notify()/notifyAll()或超时到期唤醒方式显式必须其他线程主动唤醒中断响应可中断抛出InterruptedException状态转换RUNNABLE → WAITING → BLOCKED → RUNNABLE源码级理解// ObjectMonitor::waitOpenJDK 8voidObjectMonitor::wait(jlong millis,boolinterruptable,TRAPS){// 1. 将当前线程封装为 ObjectWaiterObjectWaiternode(Self);node._TStateObjectWaiter::TS_WAIT;// 2. 加入 _WaitSetAddWaiter(node);// 3. 释放锁关键exit(true,THREAD);// _owner NULL, _recursions 0// 4. 挂起线程parkif(node._notified0){Self-_ParkEvent-park();// 状态变为 WAITING}// 5. 被唤醒后重新竞争锁EnterI(THREAD);// 进入 _EntryList状态变为 BLOCKED}2. 六大维度全面对比对比维度BLOCKEDWAITING触发方式被动——竞争锁失败主动——已持有锁调用wait()锁持有状态不持有锁调用wait()前持有调用后释放所在队列_EntryList/_cxq_WaitSet唤醒机制自动——锁释放后 JVM 唤醒显式——需notify/notifyAll中断响应❌ 不可中断✅ 可中断抛InterruptedException唤醒后状态直接 → RUNNABLE已竞争到锁先 → BLOCKED重新竞争锁→ RUNNABLE典型场景多线程竞争同一synchronized锁生产者-消费者模式、条件等待jstack 标识BLOCKED (on object monitor)WAITING (on object monitor)3. 状态转换的完整时间线对比3.1 BLOCKED 状态转换时间线 T1: Thread-A 执行 synchronized(lock) → CAS 获取锁成功 → _owner Thread-A T2: Thread-B 执行 synchronized(lock) → CAS 获取锁失败 → 进入 _EntryList T3: Thread-B 状态变为 BLOCKED T4: Thread-A 执行完 synchronized 块 → monitorexit → _owner NULL T5: JVM 从 _EntryList 唤醒 Thread-B → Thread-B CAS 获取锁 → _owner Thread-B T6: Thread-B 状态变为 RUNNABLE 状态链RUNNABLE → BLOCKED → RUNNABLE3.2 WAITING 状态转换时间线 T1: Thread-A 执行 synchronized(lock) → CAS 获取锁成功 → _owner Thread-A T2: Thread-A 在 synchronized 内调用 lock.wait() → 释放锁 → _owner NULL T3: Thread-A 被封装为 ObjectWaiter 加入 _WaitSet T4: Thread-A 状态变为 WAITING T5: Thread-B 获取锁 → 执行业务 → 调用 lock.notify() T6: Thread-A 从 _WaitSet 移出 → 进入 _EntryList T7: Thread-A 状态变为 BLOCKED需重新竞争锁 T8: Thread-B 释放锁 → Thread-A 竞争成功 → _owner Thread-A T9: Thread-A 状态变为 RUNNABLE 状态链RUNNABLE → WAITING → BLOCKED → RUNNABLE关键差异WAITING 被唤醒后不是直接 RUNNABLE而是先 BLOCKED因为notify调用时锁可能仍被notify调用者持有线程必须重新竞争。4. 中断响应的差异——不可中断 vs 可中断4.1 BLOCKED 不可中断ThreadblockedThreadnewThread(()-{synchronized(lock){// 如果 lock 已被其他线程持有此线程进入 BLOCKED}});blockedThread.start();Thread.sleep(100);// 确保进入 BLOCKEDblockedThread.interrupt();// ❌ 无效BLOCKED 状态不响应中断// blockedThread 继续等待锁释放不会抛出 InterruptedException原因BLOCKED是 JVM 管理的锁竞争状态线程在操作系统层面是pthread_cond_wait或自旋不检查中断标志。4.2 WAITING 可中断ThreadwaitingThreadnewThread(()-{synchronized(lock){try{lock.wait();// 进入 WAITING}catch(InterruptedExceptione){// ✅ 响应中断从 _WaitSet 移出重新竞争锁后抛出异常System.out.println(Interrupted!);}}});waitingThread.start();Thread.sleep(100);waitingThread.interrupt();// ✅ 有效抛出 InterruptedException原因wait()内部调用pthread_cond_wait会检查中断标志发现中断后设置Thread.interrupted true唤醒后抛出InterruptedException。5. 与 LockSupport.park 的 WAITING 对比LockSupport.park()产生的 WAITING 与Object.wait()不同维度Object.wait()的 WAITINGLockSupport.park()的 WAITING锁释放✅ 释放 Monitor 锁❌ 不释放任何锁与锁无关调用前提必须在synchronized内任意位置唤醒方式notify()/notifyAll()LockSupport.unpark(thread)中断响应抛出InterruptedException不抛异常仅设置中断标志所属队列_WaitSet线程私有的Parker对象典型应用生产者-消费者AQS 框架的线程阻塞// LockSupport.park 的 WAITINGThreadparkThreadnewThread(()-{LockSupport.park();// 进入 WAITING不释放任何锁});parkThread.start();Thread.sleep(100);parkThread.interrupt();// 不抛异常仅设置中断标志// parkThread.isInterrupted() trueLockSupport.unpark(parkThread);// 唤醒6. jstack 实战——区分 BLOCKED 和 WAITING6.1 BLOCKED 的 jstack 特征Thread-B #12 prio5 os_prio0 tid0x00007f8b4c003800 nid0x5202 waiting for monitor entry [0x000070000c4a4000] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.Service.process(Service.java:45) - waiting to lock 0x000000076b5c7d58 (a java.lang.Object) - locked 0x000000076b5c7d68 (a java.lang.Object)关键词waiting for monitor entry、- waiting to lock。6.2 WAITING 的 jstack 特征Thread-A #11 prio5 os_prio0 tid0x00007f8b4c002800 nid0x5201 in Object.wait() [0x000070000c3a3000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on 0x000000076b5c7d58 (a java.lang.Object) at com.example.Service.process(Service.java:30) - locked 0x000000076b5c7d58 (a java.lang.Object)关键词in Object.wait()、- waiting on、- locked表示调用 wait 前持有该锁。6.3 排查案例# 统计 BLOCKED 线程jstackpid|grep-cjava.lang.Thread.State: BLOCKED# 统计 WAITING 线程jstackpid|grep-cjava.lang.Thread.State: WAITING# 如果 BLOCKED 线程持续增长 → 锁竞争严重需优化锁粒度# 如果 WAITING 线程持续增长 → 可能生产者阻塞或 notify 遗漏7. 常见误区澄清误区正确理解BLOCKED 和 WAITING 都是阻塞❌ BLOCKED 是锁竞争失败WAITING 是主动条件等待WAITING 不释放锁❌Object.wait()会释放Monitor 锁LockSupport.park()不释放notify后线程立即执行❌notify后线程先进入 BLOCKED 重新竞争锁BLOCKED 可以被 interrupt❌ BLOCKED不可中断WAITING 可以sleep会进入 BLOCKED❌sleep进入 TIMED_WAITING不释放锁join会释放锁❌join与锁无关进入 WAITING 等待目标线程结束所有 WAITING 都在 _WaitSet❌LockSupport.park的 WAITING 在线程私有的 Parker 中8. 面试官追问与高分回答模板追问 1“BLOCKED 和 WAITING 有什么区别”低分回答“BLOCKED 是被动阻塞WAITING 是主动等待。”太浅高分回答BLOCKED 和 WAITING 的本质区别在于Monitor 对象中的位置和锁状态触发原因BLOCKED 是竞争synchronized锁失败被 JVM 放入_EntryListWAITING 是已持有锁的线程调用Object.wait()主动释放锁并进入_WaitSet。锁状态BLOCKED 线程不持有锁WAITING 线程调用wait()前持有锁调用后释放锁。唤醒机制BLOCKED 由 JVM 自动唤醒锁释放后从_EntryList取出WAITING 必须其他线程显式notify/notifyAll。中断响应BLOCKED不可中断WAITING可中断抛出InterruptedException。唤醒后状态BLOCKED 直接 → RUNNABLE已获取锁WAITING 先 → BLOCKED重新竞争锁→ RUNNABLE。类比BLOCKED 像排队买票还没轮到你被动等待WAITING 像买完票去休息区坐着已放弃位置等叫号。追问 2“为什么 wait 被唤醒后要先进入 BLOCKED”高分回答这是 JVM 的安全设计原因有二锁可能仍被持有notify在synchronized块内调用调用时锁仍被notify调用者持有。如果线程直接 RUNNABLE会在锁未释放时执行破坏互斥性多线程竞争notifyAll可能唤醒多个线程它们必须重新竞争锁只有一个能成功。完整流程线程 A 调用wait()→ 释放锁 → 进入_WaitSetWAITING线程 B 获取锁 →notify()→ 线程 A 被标记为’待唤醒’线程 B 退出synchronized→ 释放锁线程 A 从_WaitSet移出 → 进入_EntryListBLOCKED→ 竞争锁 → RUNNABLE。所以notify是’通知’不是’授权’线程必须重新竞争锁。追问 3“BLOCKED 能被中断吗为什么”高分回答BLOCKED不能被中断。原因实现层面BLOCKED 线程在操作系统层面是pthread_mutex_lock或自旋等待不检查中断标志语义层面中断意味着’取消操作’但 BLOCKED 线程只是在等待获取锁获取锁后仍需执行业务逻辑。如果中断后跳过锁获取会破坏互斥性设计层面JVM 认为锁竞争是短暂的不应被中断打断。如果长时间 BLOCKED说明锁粒度过粗或死锁应通过优化代码解决而非中断。如果需要’可中断的锁获取’应使用ReentrantLock.lockInterruptibly()它通过 AQS 的acquireInterruptibly实现在 park 期间检查中断标志。追问 4“LockSupport.park 的 WAITING 和 Object.wait 的 WAITING 有什么区别”高分回答两者虽然都是 WAITING 状态但实现机制和语义完全不同锁释放Object.wait()释放 Monitor 锁LockSupport.park()与锁无关不释放任何锁调用前提wait()必须在synchronized内park()任意位置唤醒方式wait()需notify/notifyAllpark()需unpark(thread)中断响应wait()抛InterruptedExceptionpark()不抛异常仅设置中断标志底层实现wait()使用 Monitor 的_WaitSetpark()使用线程私有的Parker对象pthread_cond_wait。典型应用Object.wait()生产者-消费者模式LockSupport.park()AQS 框架的线程阻塞ReentrantLock、CountDownLatch底层。追问 5“jstack 中怎么区分 BLOCKED 和 WAITING”高分回答jstack 中区分 BLOCKED 和 WAITING 有三个特征状态标识BLOCKED 显示BLOCKED (on object monitor)WAITING 显示WAITING (on object monitor)或WAITING (parking)栈顶方法BLOCKED 的栈顶是业务代码如at com.example.Service.processWAITING 的栈顶是at java.lang.Object.wait或at sun.misc.Unsafe.park锁信息BLOCKED 显示- waiting to lock 0x...等待获取锁WAITING 显示- waiting on 0x...已在等待条件和- locked 0x...调用 wait 前持有该锁。实战排查BLOCKED 线程数持续增长 → 锁竞争严重检查锁粒度和持有时间WAITING 线程数持续增长 → 可能生产者阻塞或notify遗漏检查条件变量逻辑。追问 6“如果一个线程同时 BLOCKED 和 WAITING可能吗”高分回答不可能。一个线程在任意时刻只能有一种状态。但存在’状态切换的瞬时’线程从 WAITING 被唤醒后在重新竞争锁的期间是 BLOCKED。这不是’同时两种状态’而是’先后两种状态’。另外Thread.getState()返回的是快照状态调用瞬间线程可能正在状态切换的边缘但 JVM 保证状态的一致性——不会出现’半 BLOCKED 半 WAITING’的中间态。9. 方案选型速查表场景推荐状态/方法说明多线程竞争同一资源BLOCKEDsynchronizedJVM 自动管理简单但不可中断生产者-消费者模式WAITINGObject.wait/notify条件等待释放锁可中断AQS 框架线程阻塞WAITINGLockSupport.park不依赖 Monitor灵活可控需要可中断的锁获取TIMED_WAITINGlockInterruptiblyReentrantLock 支持中断限时等待TIMED_WAITINGwait(timeout)超时自动唤醒防永久阻塞面试官想要的满分总结BLOCKED 和 WAITING 的区别不是被动 vs 主动这么简单而是Monitor 对象中两个不同队列_EntryListvs_WaitSet的两种不同语义BLOCKED是锁竞争的排队区——线程想获取锁但竞争失败被 JVM 放入_EntryList不持有锁只能等锁释放后自动唤醒不可中断。唤醒后直接 RUNNABLE已获取锁。WAITING是条件等待的休息区——线程已持有锁调用wait()主动释放锁并进入_WaitSet需要其他线程显式notify唤醒。可中断唤醒后先 BLOCKED重新竞争锁再 RUNNABLE。关键记忆点BLOCKED 不持有锁等锁WAITING 释放锁等条件。LockSupport.park的 WAITING 是另一套机制线程私有 Parker不释放锁与Object.wait的 WAITING 不可混淆。生产实战中jstack的waiting for monitor entry是 BLOCKEDin Object.wait()是 WAITING。BLOCKED 多说明锁竞争WAITING 多说明条件等待异常——这是排查死锁和性能瓶颈的核心线索。觉得对您有帮助麻烦点点关注啦您的关注是我创作的最大动力~