前言
由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序
完成这个协调工作, 主要涉及到四个方法
wait() / wait(long timeout): 让当前线程进入等待状态.
notify() / notifyAll(): 唤醒在当前对象上等待的线程
一.wait()方法
如果调用wait()方法,那么它会做以下三件事:
- 使当前执行代码的线程进行等待. (把线程放到等待队列中)
- 释放当前的锁
- 满足一定条件时被唤醒, 重新尝试获取这个锁
注意: wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.
也就是说,对象的引用调用wait, 首先这个对象一定是被加锁了
代码示例:
public class WaitANDNotify {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
synchronized (o1) {
System.out.println("wait 之前");
//之所以吧wait放到synchronized里面,是要确保o1对象是拿到了锁的
o1.wait();
System.out.println("wait 之后");
}
}
}
此时如果没有别的线程去调用notify方法,那么代码会一直等待,执行不到“wait之后”
注意:wait方法调用后,该线程会主动放弃CPU和资源,进入等待状态;
线程调用wait方法后,会被加进等待池中,对应的状态是WAITING、TIMED_WAITING。
二.notify()方法
notify 方法是唤醒等待的线程.
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的。
- 其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
- 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到");
- 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁
代码示例:
class notify1 {
public static void main(String[] args) {
/**
* wait操作了一下几步
* 1.释放当前的锁
* 2.让线程进入阻塞
* 3.当线程被唤醒的时候,重新获取到锁 如果一次唤醒多个锁,那么就有可能是串行执行,按照一定的顺序进行重新获取到锁
*/
Object object = new Object();
Thread t1= new Thread(() -> {
synchronized (object) {
System.out.println("wait 之前");
try {
object.wait(); //此时已经释放了当前的锁了
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("wait之后");
});
Thread t2 = new Thread(() -> {
try {
System.out.println("等待了三秒,马上进行通知");
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object) { //此时object对象已经被wait解锁了,需要重新加锁
System.out.println("进行通知");
object.notify(); //唤醒锁
}
});
t1.start();
t2.start();
}
}
运行结果:
代码解读:
- 此代码有一个对象object 和两个线程 t1 和 t2,t1 让object加锁并且进入了wait状态,t2负责唤醒object对象。
- 在唤醒 object对象时,由于它已经被wait操作释放锁了,此时需要重新加锁,然后调用 notify 方法,唤醒object对象
在Java中,当线程调用一个对象的wait()方法时,它会做几件事情:
释放当前持有的该对象的监视器锁(即互斥锁)。
使当前线程进入等待状态,并加入到该对象的等待集中。
这样做的目的是允许其他线程能够获取该对象的监视器锁,从而能够执行与该对象相关的同步代码块或同步方法。这是实现线程间协作和通信的关键步骤之一。
当另一个线程(比如t2)调用同一个对象的notify()或notifyAll()方法时,它正在持有该对象的监视器锁。这个线程是从等待集中选择一个等待的线程来唤醒的。在大多数情况下,notify()方法唤醒等待集中的一个线程(具体是哪个线程是不确定的),而notifyAll()方法会唤醒等待集中的所有线程。
重要的是要理解,wait()释放锁的目的是为了让其他线程有机会获取锁并执行同步代码。这样,其他线程(如t2)就能够执行某些操作,比如修改对象的状态,并在完成这些操作后通过notify()或notifyAll()来通知等待的线程(如t1)。
在你的示例中,t1线程通过调用object.wait();释放了object对象的锁,并进入等待状态。然后,t2线程开始执行,并在同步代码块内通过object.notify();唤醒object对象的等待集中的一个线程(在这个例子中是t1)。唤醒并不意味着t1会立即执行,它只是将t1从等待状态变为就绪状态,等待操作系统的调度来重新获取object的锁。一旦t1重新获取到锁,它就会继续执行同步代码块之后的代码。
总的来说,wait()释放锁是为了让其他线程能够访问和操作共享数据,而notify()或notifyAll()则是用来通知等待的线程状态已经改变,它们可以继续执行了。这种机制是实现线程间同步和通信的基础。