wait/notify
wait/notify
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | @Slf4j public class Test1_4_2 { public static void main(String[] args) throws InterruptedException { A1 a1 = new A1(); Thread t1 = new Thread( new Runnable() { @SneakyThrows @Override public void run() { log.info( "executing1" ); a1.f1(); } }); Thread t11 = new Thread( new Runnable() { @SneakyThrows @Override public void run() { log.info( "executing11" ); a1.f1(); } }); Thread t2 = new Thread( new Runnable() { @SneakyThrows @Override public void run() { log.info( "executing2" ); a1.f2(); } }); t1.start(); t11.start(); Thread.sleep( 50 ); t2.start(); } } @Slf4j class A1 { private Object obj1 = new Object(); public void f1() throws InterruptedException { // 1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。 synchronized (obj1) { log.info( "f1" ); // 2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。 // 5)从wait()方法返回的前提是获得了调用对象的锁。 obj1.wait(); // obj1.wait(1000); log.info( "f1-2" ); } } public void f2() throws InterruptedException { // 1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。 synchronized (obj1) { log.info( "f2" ); // 4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。 // obj1.notify(); obj1.notifyAll(); log.info( "f2-2" ); // 3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。 Thread.sleep( 2000 ); log.info( "f2-3" ); } } } |
上述例子主要说明了调用wait()、notify()以及notifyAll()时需要注意的细节,如下。
1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
5)从wait()方法返回的前提是获得了调用对象的锁。
从上述细节中可以看到,等待/通知机制依托于同步机制,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改。
为什么必须和synchronized一起使用
在 Java 里面,wait()和 notify()是 Object 的成员函数, 是基础中的基础。为什么 Java 要把wait()和 notify()放在如此 基础的类里面,而不是作为像 Thread 一类的成员函数,或者其他类 的成员函数呢?
在回答这个问题之前,先要回答为什么wait()和notify()必 须和synchronized一起使用?
上面代码的例子中 线程A调用f1(),线程B调用f2()。答案 已经很明显:两个线程之间要通信,对于同一个对象来说,一个线程 调用该对象的wait(),另一个线程调用该对象的notify(),该对象本身就需要同步!所以,在调用wait()、notify()之前,要先 通过synchronized关键字同步给对象,也就是给该对象加锁。
synchronized关键字可以加在任何对象的成员函 数上面,任何对象都可能成为锁。那么,wait()和notify()要同 样如此普及,也只能放在Object里面了。
为什么wait()的时候必须释放锁
当线程A进入synchronized(obj1)中之后,也就是对obj1上了 锁。此时,调用wait()进入阻塞状态,一直不能退出synchronized 代码块;那么,线程B永远无法进入synchronized(obj1)同步块里, 永远没有机会调用notify(),岂不是死锁了?
这就涉及一个关键的问题:在wait()的内部,会先释放锁obj1,然后进入阻塞状态,之后,它被另外一个线程用notify()唤醒, 去重新拿锁!其次,wait()调用完成后,执行后面的业务逻辑代 码,然后退出synchronized同步块,再次释放锁。
wait()内部的伪代码如下:
只有如此,才能避免上面所说的死锁问题。
参考: java并发编程的艺术 4.3.2 等待/通知机制
Java并发实现原理:JDK源码剖析 1.4 wait()与notify()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律