【Java Concurrency】sleep()、wait()、notify()、notifyAll()的用法与区别
>关于本文
本文介绍sleep()、wait()、notify()、notifyAll()方法,主要要理解:
- sleep()和wait()的区别。
- wait()与notify()、notifyAll()之前互相协调的关系。
- notify()与notifyAll()的区别。
> Thread.sleep(long),睡眠指定时间
此方法是让线程睡眠指定时间,不释放锁(睡觉,当然要上锁,这个还用说么)。
此方法我貌似很少用,又似乎很常用。因为,在正式代码中我很少用到,而在测试代码中,却又经常用来模拟某某业务需时几秒的阻塞。
public class Sleep { public static void main(String[] args) { System.out.println("Start..."); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("End..."); } }
一般,还有个更易读的写法,一样的效果
import java.util.concurrent.TimeUnit; public class Sleep { public static void main(String[] args) { System.out.println("Start..."); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("End..."); } }
> Object.wait(),等待(条件)
线程转到等待状态,释放锁。(既然等待,当然得释放锁了,我们在等待、迎接贵宾时,也是敞开着大门的,哈哈)
在持有锁的情况下才能调用此方法,通常搭配外层的循环以判断是否继续等待。
wait方法可以执行时间,也可不,由notify方法唤醒。
晚餐、客人、服务员的例子

/** * 晚餐 */ public class Dinner { private String mainDish; // 主菜 public String getMainDish() { return mainDish; } public void setMainDish(String mainDish) { this.mainDish = mainDish; } }

/** * 客人 */ public class Customer extends Thread { private Dinner d; public Customer(Dinner d) { super(); this.d = d; } @Override public void run() { synchronized (d) { while (d == null || d.getMainDish() == null) { try { System.out.println(currentThread().getName() + ", customer start to wait."); d.wait(); System.out.println(currentThread().getName() + ", customer end to wait."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } d.setMainDish(null); // 相当于把菜吃掉 System.out.println(currentThread().getName() + ", customer eat the food."); } super.run(); } }

/** * 服务员 */ public class Waiter extends Thread { private Dinner d; public Waiter(Dinner d) { super(); this.d = d; } @Override public void run() { synchronized (d) { d.notify(); d.setMainDish("牛扒"); // 相当于上菜 System.out.println(currentThread().getName() + ", waiter notify."); } super.run(); } }

import java.util.concurrent.TimeUnit; public class HowToUse { public static void main(String[] args) { Dinner d = new Dinner(); Customer c = new Customer(d); Waiter w = new Waiter(d); c.start(); /* 等待一段时间,目的让Customer线程先启动 */ try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } w.start(); } }
日志:
Thread-0, customer start to wait. Thread-1, waiter notify. Thread-0, customer end to wait. Thread-0, customer eat the food.
当然,也可执行时长停止等待了:

public class Wait { public static void main(String[] args) { System.out.println("Start..."); String s = ""; synchronized (s) { try { s.wait(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("End..."); } }
> Object.notify()和Object.notifyAll(),唤醒等待的线程
一个是通知一个线程(通知哪一个线程是不确定的哦),另一个是唤醒全部线程。
将HowToUse方法修改下:

import java.util.concurrent.TimeUnit; public class HowToUse { public static void main(String[] args) { Dinner d = new Dinner(); Customer c1 = new Customer(d); Customer c2 = new Customer(d); Waiter w = new Waiter(d); c1.start(); c2.start(); /* 等待一段时间,目的让Customer线程先启动 */ try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } w.start(); } }
就可以看到:
Thread-0, customer start to wait.
Thread-1, customer start to wait.
Thread-2, waiter notify.
Thread-0, customer end to wait.
Thread-0, customer eat the food.
Thread-0、Thread-1分别代表两个客人,waiter唤醒了Thread-0,属于Thread-0的客人停止等待,去吃大餐了。
将notify修改成notifyAll:

/** * 服务员 */ public class Waiter extends Thread { private Dinner d; public Waiter(Dinner d) { super(); this.d = d; } @Override public void run() { synchronized (d) { d.notifyAll(); d.setMainDish("牛扒"); // 相当于上菜 System.out.println(currentThread().getName() + ", waiter notify."); } super.run(); } }
可以看到日志:
Thread-0, customer start to wait. Thread-1, customer start to wait. Thread-2, waiter notify. Thread-1, customer end to wait. Thread-1, customer eat the food. Thread-0, customer end to wait. Thread-0, customer start to wait.
Thread-2通知大家后,Thread-0、Thread-1都停止等待了,只不过Thread-1抢到了食物,吃完了,所以Thread-0又得重新等待。
> 什么时候用notify,什么时候用notifyAll?
如果各线程等待的条件不一样,那么要用notifyAll,因为用notify只通知到一个线程,而那线程的不满足跳出等待的条件,那么不就不好了吗。
比如,有一位富有的客人,要求晚餐的主菜中要有红酒才满足,与普通客人等待的条件不一样,如果服务员只准备了牛扒没有红酒,而有用notify只通知了富有的客人,那么普通的客人就没被通知到了。

/** * 富有的客人 */ public class WealthyCustomer extends Thread { private Dinner d; public WealthyCustomer(Dinner d) { super(); this.d = d; } @Override public void run() { synchronized (d) { while (d == null || d.getMainDish() == null || !d.getMainDish().contains("红酒")) { // 富有的客人,要求的主菜要有红酒 try { System.out.println(currentThread().getName() + ", wealthy customer start to wait."); d.wait(); System.out.println(currentThread().getName() + ", wealthy customer end to wait."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } d.setMainDish(null); // 相当于把菜吃掉 System.out.println(currentThread().getName() + ", wealthy customer eat the food."); } super.run(); } }

import java.util.concurrent.TimeUnit; public class HowToUse { public static void main(String[] args) { Dinner d = new Dinner(); Customer c1 = new Customer(d); WealthyCustomer c2 = new WealthyCustomer(d); Waiter w = new Waiter(d); c2.start(); // 将WealthyCustomer的线程的先启动,比较容易看到Waiter通知他 c1.start(); /* 等待一段时间,目的让Customer线程先启动 */ try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } w.start(); } }
将Waiter设置成notify,会看到如下日志:
Thread-1, wealthy customer start to wait. Thread-0, customer start to wait. Thread-2, waiter notify. Thread-1, wealthy customer end to wait. Thread-1, wealthy customer start to wait.
将Waiter设置成notifyAll,日志如下:
Thread-1, wealthy customer start to wait. Thread-0, customer start to wait. Thread-2, waiter notify. Thread-0, customer end to wait. Thread-0, customer eat the food. Thread-1, wealthy customer end to wait. Thread-1, wealthy customer start to wait.
> 参考的优秀文章
Thread.sleep(long millis) API doc
《Java编程思想》,机械工业出版社
本博客为学习、笔记之用,以笔记形式记录学习的知识与感悟。学习过程中可能参考各种资料,如觉文中表述过分引用,请务必告知,以便迅速处理。如有错漏,不吝赐教。
如果本文对您有用,点赞或评论哦;如果您喜欢我的文章,请点击关注我哦~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
2016-02-15 【开发】开发完毕,测试后,别再漏了检查了
2016-02-15 【Regular Expression】常用的正则表达式
2016-02-15 【Thread】多线程的异常处理?