目录
- 线程的状态对比:等待、驻留、监视
- 等待(waiting)和监视(blocked)的区别
- 等待(waiting)和监视(blocked)都算是阻塞吗?
- 线程池没有任务来时,所有核心线程会是等待(waiting)状态?
- 不同类型的等待
- 总结
- 等待(Wait)和驻留(Park)的区别
- 代码示例对比
- 总结
- Park状态的出现时机
- Park状态和Park线程的区别分析
线程的状态对比:等待、驻留、监视
等待(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%时间)
- 设计用途就是等待事件/任务
- 执行的工作是次要的,等待是主要的