当前位置: 首页 > news >正文

从sleep与wait的锁释放差异,深入剖析Java线程状态转换

1. sleep与wait的核心差异解析

多线程编程中,sleep()wait()是最容易混淆的两个方法。刚入行时我也经常搞混,直到有次线上服务出现线程阻塞,用jstack分析dump文件时才真正理解它们的区别。先说结论:sleep是抱着锁睡觉,wait会先交出锁再等待

1.1 锁释放行为的本质区别

先看这段代码示例:

Object lock = new Object(); // 线程A执行 synchronized (lock) { System.out.println("线程A拿到锁"); Thread.sleep(3000); // 睡眠期间不释放锁 System.out.println("线程A释放锁"); } // 线程B执行 synchronized (lock) { System.out.println("线程B拿到锁"); }

运行时会发现:线程B必须等线程A完全执行完sleep的3秒后才能获取锁。这说明sleep()虽然让线程暂停执行,但不会释放已持有的锁

而换成wait()的版本:

Object lock = new Object(); // 线程A synchronized (lock) { System.out.println("线程A拿到锁"); lock.wait(3000); // 等待期间释放锁 System.out.println("线程A重新获得锁"); } // 线程B synchronized (lock) { System.out.println("线程B拿到锁"); }

这时线程B会立即获得锁并执行,3秒后线程A才会继续。这就是wait()的关键特性:主动释放锁并进入等待

1.2 状态转换的底层原理

通过jstack观察线程状态会更清晰:

  • sleep()会使线程进入TIMED_WAITING (sleeping)状态
  • wait()会使线程进入WAITING (on object monitor)TIMED_WAITING (on object monitor)状态

这两种状态的根本区别在于:

  • sleep时的线程仍然持有monitor锁(通过synchronized获取)
  • wait时的线程会将monitor锁归还给对象,这也是为什么wait必须在同步块中调用

我曾经在排查一个数据库连接池问题时,发现所有工作线程都卡在WAITING状态。最终发现是因为某个事务处理超时后没有正确调用notify,导致线程永久等待。这个案例充分说明了理解状态转换的重要性。

2. 线程状态转换全景剖析

2.1 Java线程的6种状态

根据Thread.State枚举,Java线程共有6种状态:

  1. NEW:新建未启动
  2. RUNNABLE:可运行(包括正在运行和就绪)
  3. BLOCKED:等待获取锁
  4. WAITING:无限期等待
  5. TIMED_WAITING:有限期等待
  6. TERMINATED:终止

重点看与锁相关的状态转换:

graph LR BLOCKED-->|获取锁|RUNNABLE RUNNABLE-->|wait()|WAITING WAITING-->|notify()|BLOCKED RUNNABLE-->|sleep()|TIMED_WAITING TIMED_WAITING-->|时间到|RUNNABLE

2.2 状态转换的典型场景

场景1:锁竞争引发的BLOCKED

// 线程1 synchronized(lock) { Thread.sleep(10000); // 持有锁睡眠 } // 线程2 synchronized(lock) { // 这里会阻塞 System.out.println("获取到锁"); }

用jstack查看时,线程2会显示:

"Thread-2" #12 prio=5 blocked - waiting to lock <0x0000000712345678>

场景2:wait/notify流程

// 等待线程 synchronized(lock) { lock.wait(); // 释放锁进入WAITING // 被唤醒后需要重新获取锁 } // 唤醒线程 synchronized(lock) { lock.notifyAll(); }

这里有个关键点:被notify唤醒的线程会从WAITING变为BLOCKED,必须再次获取锁才能继续执行。

3. 实战问题排查指南

3.1 使用jstack分析线程阻塞

去年我们系统出现过一次性能骤降,通过以下步骤定位问题:

  1. 获取线程dump
jstack -l <pid> > thread_dump.txt
  1. 分析关键信息
"DB-Connection-Thread-5" #25 daemon prio=5 WAITING at java.lang.Object.wait(Native Method) - waiting on <0x000000076bf45678> at com.mysql.jdbc.ConnectionImpl.waitForLock "HTTP-Worker-Thread-3" #33 prio=5 BLOCKED - waiting to lock <0x000000076bf45678>
  1. 结论:数据库连接线程持锁时间过长,导致HTTP工作线程阻塞

3.2 使用VisualVM监控

对于图形化工具,我推荐JDK自带的VisualVM:

  1. 打开jvisualvm
  2. 选择目标Java进程
  3. 查看"线程"选项卡
  4. 右键点击线程可以执行线程dump

特别有用的功能是可以看到:

  • 线程状态随时间的变化
  • 持有锁和等待锁的关系图
  • 死锁自动检测

4. 高级话题:Lock与synchronized的区别

4.1 状态表现的差异

同样的锁竞争场景,使用ReentrantLock时线程状态与synchronized不同:

Lock lock = new ReentrantLock(); // 线程1 lock.lock(); try { Thread.sleep(10000); } finally { lock.unlock(); } // 线程2 lock.lock(); // 这里会进入WAITING而不是BLOCKED try { System.out.println("获取到锁"); } finally { lock.unlock(); }

这是因为AQS的实现机制不同,jstack会显示:

"Thread-2" #12 prio=5 waiting at sun.misc.Unsafe.park(Native Method)

4.2 选择建议

根据我的经验:

  • 简单场景用synchronized(代码更简洁)
  • 需要超时或中断特性时用Lock
  • 高并发竞争激烈时用Lock(性能更好)

曾经在秒杀系统中,将synchronized改为ReentrantLock后,TPS提升了近40%。但要注意正确使用try-finally释放锁。

5. 最佳实践与避坑指南

  1. 永远不要在循环外调用wait()

    • 经典写法:
    synchronized(lock) { while(!condition) { lock.wait(); } }
  2. 区分sleep和wait的唤醒方式

    • sleep只能等时间到或被interrupt
    • wait可以被notify/notifyAll唤醒
  3. 锁粒度控制

    • 我遇到过的一个坑:在HashMap的全局锁里执行耗时IO操作
    • 应该拆分为:细粒度锁 + 无锁IO操作
  4. 状态监控建议

    • 生产环境建议定期采集线程dump
    • 关键指标:BLOCKED线程数、WAITING超时情况

记得有次凌晨处理告警,发现大量TIMED_WAITING线程,原来是某定时任务没有设置超时时间。这个经历让我养成了对所有阻塞操作都设置超时的习惯。

http://www.jsqmd.com/news/1196647/

相关文章:

  • 2026南京工业冰块哪家强?六大品牌实测排名揭晓 - 热点咨讯
  • 深入解析AM572x GPMC异步时序:从原理到NOR/NAND Flash配置实战
  • 从教育焦虑到个性化学习:破解孩子学习动力的密码
  • 从“泡池子”到WXG Offer:一位25届学生的腾讯暑期实习闯关实录
  • 【C++提高】高精度加法
  • 基于西门子PLC的智能物流/快递分类设计1112(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_
  • 基于PLC的四层电梯自动控制系统设计13(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 北京欧米茄回收价格查询和靠谱回收平台实测排行(2026年7月最新数据) - 天价名表回收平台
  • 从零到一:深入理解ASCII码在C语言中的核心应用
  • 利用MATLAB深度学习工具箱实现CNN图像分类:从数据准备到模型评估全流程解析
  • 电线电缆选购指南:金环宇ZC-BVR4平方多股软线实测解析
  • 长沙浏阳市适老化改造|维小达|适老厨房、卫生间、卧室改造、全屋无障碍改造、适老定制、老旧小区居家商用一站式适老化设计施工养护服务 - 一点传媒
  • 基于RF430CL331H的动态NFC标签设计:从硬件到软件实战指南
  • Flink SQL Client实战:从零配置到流式SQL任务一键提交集群
  • 南京宝珀回收价格查询和靠谱回收平台实测排行(2026年7月最新) - 嘉价奢侈品回收平台
  • 汽车DLP HUD系统管理芯片TPS99000S-Q1深度解析与设计实践
  • 无源无感·合规可控:镜像视界跨镜无缝接力,筑牢涉密军工安防绝对防线专项技术白皮书 · 军工涉密专属版
  • 停止序列不生效?ChatGPT响应截断异常诊断手册,含curl/postman/Python SDK三端验证清单,限时开源
  • 从选型到焊接:阻容封装尺寸速查与实战应用指南
  • 基于plc的物料分拣1112(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_
  • 计算机毕业设计之基于springBoot和vue的篮球比赛管理系统
  • 为什么黑客宁愿教你技术,也不愿意直接帮你!
  • 《命若琴弦》读后感:在虚无中寻找意义,于绝境中奏响生命之歌
  • PX4无人机 - 从键盘控制到Offboard模式实战解析
  • ROS2大型项目启动架构:Launch分层设计与参数治理实战
  • MQTT与MQ:从协议到平台,如何为你的项目选择正确的消息传递方案
  • 深圳卡板定制厂家排行:合规性与定制能力实测对比 - 互联网科技品牌测评
  • 能独立开发AI智能体的供应商盘点:自研模型是分水岭 - 科技焦点
  • 真实事件影视改编技术方案:从剧本数字化到后期制作全流程
  • 位置式PID与增量式PID的代码实现与电机控制实战