线程的状态对比:等待、驻留、监视(阻塞、等待)
目录
线程的状态对比:等待、驻留、监视
等待(waiting)和监视(blocked)的区别
等待(waiting)和监视(blocked)的区别 ?
官方线程状态定义
public enum State { NEW, RUNNABLE, BLOCKED, // 阻塞 WAITING, // 等待 TIMED_WAITING, // 超时等待 TERMINATED }
核心区别对比
特性 BLOCKED(阻塞) WAITING(等待) 触发条件 等待进入synchronized同步块 调用Object.wait()、Thread.join()、LockSupport.park() 唤醒方式 获取到监视器锁时自动唤醒 需要其他线程主动唤醒(notify/notifyAll) 锁的状态 在竞争锁,但还没拿到 已经释放了锁,在等待条件 使用场景 简单的互斥访问 复杂的线程协作 超时机制 无超时,一直等待锁 可以有超时(TIMED_WAITING)或无超时 总结
关键区别记忆点:
- BLOCKED:"我想进去,但门锁着,我在门口等着"
- WAITING:"我主动休息了,你好了叫我"
游戏服务器中的指导原则:
- BLOCKED过多 → 锁竞争激烈,需要优化锁策略
- WAITING正常 → 线程协作,是设计的一部分
- 监控两者比例 → BLOCKED应该远少于WAITING
- 优先使用java.util.concurrent → 比synchronized + wait/notify更高效
等待(waiting)和监视(blocked)都算是阻塞吗?
都算是阻塞吗?
从Java线程状态机来看:BLOCKED和WAITING是两种不同的状态,但都可以被认为是广义的"阻塞"。
// Java线程状态机中的关系 RUNNABLE → BLOCKED // 等待锁 → 广义阻塞 RUNNABLE → WAITING // 主动等待 → 广义阻塞 RUNNABLE → TIMED_WAITING // 超时等待 → 广义阻塞
详细分析
- 从CPU执行角度:都是阻塞
public class CpuPerspective { public static void main(String[] args) { // 无论是BLOCKED还是WAITING状态: // - 线程都不在CPU上执行指令 ✓ // - 都不消耗CPU计算资源 ✓ // - 都在等待某种条件满足 ✓ // 因此从CPU调度器角度看,它们都是"不运行"的状态 } }
- 从阻塞原因角度:有重要区别
阻塞类型 原因 恢复条件 同步阻塞 (BLOCKED) 竞争同步锁 锁可用时自动恢复 等待阻塞 (WAITING) 主动放弃CPU 需要其他线程显式唤醒 I/O阻塞 等待I/O操作 I/O完成时恢复
线程池没有任务来时,所有核心线程会是等待(waiting)状态?
线程池中没有任务时,核心线程会处于WAITING状态,但具体机制有所不同。
不同类型的等待
- 使用LinkedBlockingQueue的线程池
public class LinkedBlockingQueueExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>() // 无界队列
);
executor.prestartAllCoreThreads();
// 空闲线程状态: WAITING
// 阻塞在: java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject
// 具体是: LinkedBlockingQueue.take() 方法中的condition.await()
}
}
- 使用SynchronousQueue的线程池
public class SynchronousQueueExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
// 内部使用SynchronousQueue
// 空闲线程状态: WAITING
// 阻塞在: SynchronousQueue的transfer方法
// 等待其他线程提交任务
}
}
- 使用ArrayBlockingQueue的线程池
public class ArrayBlockingQueueExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10) // 有界队列
);
// 空闲线程状态: WAITING
// 阻塞在: ArrayBlockingQueue的condition.await()
}
}
不同的队列不同的实现:
/**
* LinkedBlockingQueue:
* - 空闲线程: WAITING on ConditionObject
* - 队列空时阻塞在take()
*
* SynchronousQueue:
* - 空闲线程: WAITING on transfer stack
* - 没有缓冲区,直接传递
*
* DelayQueue:
* - 空闲线程: TIMED_WAITING
* - 等待特定延迟时间
*/
/**
* 空闲的核心线程资源占用:
* - 内存: 每个线程约1MB栈内存 (通过-Xss设置)
* - 内核资源: 每个线程占用操作系统线程结构
* - CPU: 几乎为0 (WAITING状态不消耗CPU)
*
* 示例: 10个核心线程空闲
* - 内存: 10MB
* - CPU: 0%
* - 状态: 全部WAITING
*/
总结
是的,线程池没有任务时,核心线程处于WAITING状态,这是正常且期望的行为:
正常现象:
- 所有空闲核心线程都处于WAITING状态
- 阻塞在工作队列的获取方法上(如take())
- 不消耗CPU资源,只占用少量内存
简单来说:WAITING的空闲线程就像待命的士兵,随时准备执行任务,这是线程池高效工作的基础机制!
等待(Wait)和驻留(Park)的区别
方面 | Wait |
Park |
---|---|---|
所属包 | java.lang.Object |
java.util.concurrent.locks.LockSupport |
需要锁吗 | ✅ 必须在synchronized块内 | ❌ 不需要持有锁 |
会释放锁吗 | ✅ 会释放锁 | ❌ 不涉及锁操作 |
唤醒方式 | notify()/notifyAll() |
unpark(thread) |
超时支持 | ✅ wait(timeout) |
✅ parkNanos(timeout) |
中断响应 | ✅ 抛出InterruptedException | ✅ 直接返回,不抛异常 |
无论是等待还是驻留,线程在WAITING状态下的资源占用模式是相同的:
- CPU占用:几乎为0
- 内存占用:固定(线程栈大小)
- 系统资源:少量内核结构
代码示例对比
- Wait 的使用
public class WaitExample {
private static final Object lock = new Object();
public void doWait() throws InterruptedException {
synchronized (lock) { // 必须先获得锁
System.out.println("准备wait,会释放锁");
lock.wait(); // 释放锁,进入WAITING状态
System.out.println("被notify唤醒了,重新获得锁");
}
}
public void doNotify() {
synchronized (lock) { // 必须获得同一把锁
lock.notify(); // 唤醒一个等待线程
}
}
}
- Park 的使用
public class ParkExample {
public void doPark() {
System.out.println("准备park,不需要锁");
LockSupport.park(); // 直接挂起,不需要锁
System.out.println("被unpark唤醒了");
}
public void doUnpark(Thread thread) {
LockSupport.unpark(thread); // 随时可以唤醒指定线程
}
}
总结
简单记忆:
Wait
:"老式"方式,必须配合synchronized
使用,会释放锁Park
:"现代"方式,更灵活,不需要锁,性能更好
在并发编程中的选择:
- 需要与
synchronized
配合时用Wait
- 在
java.util.concurrent
包中的高级并发工具中用Park
- 需要精确控制特定线程时用
Park
VisualVM中看到:
- "Wait" → 传统的
wait()/notify()
机制 - "Park" → 现代的
LockSupport.park()/unpark()
机制
两者都是等待,但底层机制和适用场景完全不同!🎯
Park状态的出现时机
- Java 5 引入JUC包时出现
- 主要在各种并发工具内部使用
- 线程池、队列、锁等底层实现
现状:
- ✅ Park/Unpark: 现代并发首选,框架广泛使用
- ⚠️ Wait/Notify: 遗留代码、简单场景、教学示例
- 🎯 推荐: 直接使用JUC高级工具,而不是手动park/wait
简单历史总结:
1996-2004: wait/notify 为主流
2004-现在: park/unpark + JUC工具 成为主流
现在: 95%+的新代码使用JUC,而不是直接wait/notify
现代Java开发中,我们更应该关注如何使用好JUC工具,而不是纠结于底层的park/wait选择!🎯
Park状态和Park线程的区别分析
方面 | Park状态 | Park线程 |
---|---|---|
定义 | 线程的瞬时状态 | 线程的身份性质 |
时间范围 | 短暂的,可变化的 | 相对持久的 |
关注点 | 线程当前在干什么 | 线程的用途和角色 |
简单判断:
- 如果线程偶尔park一下 → 只是处于Park状态
- 如果线程主要工作就是park等待 → 是Park线程
Park线程的判断标准:
- 主要工作时间处于Park状态 (>80%时间)
- 设计用途就是等待事件/任务
- 执行的工作是次要的,等待是主要的
一段代码展示等待、驻留、监视
状态名称 | 技术术语 | 触发方式 | 核心特点 |
---|---|---|---|
监视 | BLOCKED |
锁竞争失败 | 被动等待,不释放资源 |
等待 | WAITING |
wait() 调用 |
主动释放锁,等待条件 |
驻留 | PARK |
park() 调用 |
轻量级,不涉及锁操作 |
// 核心代码如下
// 🔒 BLOCKED - 锁竞争
synchronized(lock) { ... }
// ⏳ WAITING - 条件等待
lock.wait();
// 🅿️ PARK - 轻量驻留
LockSupport.park();
LockSupport.unpark(thread);
// 三种线程状态演示
public class ThreadStateDemo {
private static Object lock = new Object();
private static Thread parkThread;
public static void main(String[] args) {
// 🔒 阻塞状态 - 线程竞争锁失败
new Thread(() -> {
synchronized(lock) {
sleep(2000); // 持有锁
}
}).start();
new Thread(() -> {
synchronized(lock) { // 🟡 这里会BLOCKED
// 获得锁后执行
}
}).start();
// ⏳ 等待状态 - 主动释放锁等待
new Thread(() -> {
synchronized(lock) {
lock.wait(); // 🟡 释放锁,进入WAITING
// 被notify后继续
}
}).start();
// 🅿️ 驻留状态 - 轻量级等待
parkThread = new Thread(() -> {
LockSupport.park(); // 🟡 进入PARK状态
// 被unpark后继续
});
parkThread.start();
LockSupport.unpark(parkThread); // 唤醒
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (Exception e) {}
}
}
BLOCKED、WAITING、TIMED_WAITING状态进入方法
BLOCKED:
-
进入 synchronized 竞争锁
synchronized(lock) { // 锁被占用 → BLOCKED // ... }
-
wait 后重新竞争锁
synchronized (obj) { obj.wait(); // 释放锁 → WAITING // 被notify()唤醒后,重新竞争锁失败后还是BLOCKED状态 // 被唤醒后重新竞争锁 → 可能BLOCKED }
WAITING:
- Object#wait() Object.wait with no timeout
- Thread#join() Thread.join with no timeout
- LockSupport#park() LockSupport.park
TIMED_WAITING:
- Thread#sleep Thread.sleep
- Object#wait(long) Object.wait with timeout
- Thread##join(long) Thread.join with timeout
- LockSupport#parkNanos LockSupport.parkNanos
- LockSupport#parkUntil LockSupport.parkUntil
源码:
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
广义阻塞的定义
public enum State {
NEW,
RUNNABLE,
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 超时等待
TERMINATED
}
狭义: "我在门口等着进房间" → BLOCKED
广义: "我在任何地方等待" → 所有不消耗CPU的状态
广义阻塞 = 线程不消耗CPU时间的状态:包括:BLOCKED, WAITING, TIMED_WAITING。
狭义阻塞:BLOCKED。
-
BLOCKED: 等待锁,CPU≈0%
-
WAITING: 等待条件,CPU≈0%
-
TIMED_WAITING: 超时等待,CPU≈0%
-
RUNNABLE: 可变 (真正消耗CPU)
-
NEW/TERMINATED: CPU=0% (未开始/已结束)