Object 中的 wait() 与 notify() 方法有什么用?
1.wait()方法和notify()方法
wait()
方法和notify()
方法是用于线程间的通信和同步的方法。由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知。但是有时候我们希望合理的协调多个线程之间的执行顺序。这时候就需要用到 Object
中的 wait()
方法和notify()
方法了。
1.1 wait() 方法
如果一个线程调用了wait()
方法,那么它会经历以下几个阶段:
-
线程进入等待状态:调用
wait()
方法会导致当前线程进入等待状态,暂时停止执行。 -
释放对象锁:在调用
wait()
方法之后,线程会释放它所持有的对象锁。这样,其他线程就有机会获取该对象锁,并执行相应的操作。 -
等待其他线程的通知:一旦线程进入等待状态,它会等待其他线程调用相同对象上的
notify()
方法或notifyAll()
方法来通知它恢复执行。线程会保持等待状态,直到被通知。
补充:可选的等待时间:wait()
方法还可以传入一个可选的等待时间参数,用于限制线程等待的时间长度。如果超过指定的等待时间,线程会自动被唤醒。
我们来看看下面的案例:
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
System.out.println("wait()方法之前");
try {
//要处理 wait() 的异常
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait()方法之后");
});
t1.start();
}
}
结果:
结果抛出异常了,为什么呢?原因是:wait()
方法必须要和synchronized
搭配使用!既然wait()
是暂停执行并放弃锁,你连锁都没有何谈放弃呢?
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
//wait() 必须要和 synchronized 搭配使用
synchronized (object){
System.out.println("wait()方法之前");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait()方法之后");
}
});
t1.start();
}
}
结果:
这时 t1 线程已经阻塞,等待着有缘人使用notify()
把它唤醒。如果它不想等待有缘人,这里也可以使用定时的wait(timeout)
方法。
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
synchronized (object){
System.out.println("wait()方法之前");
try {
//1000毫秒后唤醒
object.wait(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait()方法之后");
}
});
t1.start();
}
}
结果:
wait 结束等待的条件:
- 其他线程调用该对象的
notify
方法。 wait
等待时间超时,wait
方法提供一个带有timeout
参数的版本, 来指定等待时间。- 其他线程调用该等待线程的
interrupted
方法, 导致wait
抛出InterruptedException
异常。
1.2 notify() 方法
只有wait()
可不行呀,不然一直阻塞下去不是办法。所以我们需要使用notify()
方法。
对于notify()
方法:
- 唤醒等待的线程:调用
notify()
方法会唤醒在相同对象上(锁)等待的一个线程。如果有多个线程在等待,那么具体唤醒哪个线程是不确定的,取决于操作系统的调度。 - 通知等待线程可以继续执行:一旦线程被唤醒,它会从
wait()
方法的阻塞中恢复过来,并且可以继续执行后续的操作。 - 不释放对象锁:与
wait()
方法不同,notify()
方法并不会释放对象的锁。唤醒的线程需要等待调用notify()
方法的线程释放对象锁后才能继续执行。
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(()->{
System.out.println("t1: wait()方法之前");
try {
synchronized (object){
object.wait();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1: wait()方法之后");
});
Thread t2 = new Thread(()->{
System.out.println("t2: notify()方法之前");
//notify() 也要与 synchronized 配合使用
synchronized (object){
object.notify();
}
System.out.println("t2: notify()方法之后");
});
t1.start();
//这里的 sleep 是确保先让 t1 阻塞
Thread.sleep(500);
t2.start();
}
}
1.3 notifyAll() 方法
对于notify()
方法,如果有多个线程在等待,那么具体唤醒哪个线程是不确定的 。而 notifyAll()
方法是唤醒所有等待的线程(同一个锁),然后让这些线程继续争夺锁。
案例:有三个线程,分别只能打印A、B、C。现在要求控制这三个线程分别进行打印,顺序必须为 A、B、C。
class Demo4{
//记录当前该打印的字母
static char state = 'A';
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(()->{
synchronized (lock){
while(state != 'A'){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("A");
//A 已经打印了,该打印 B
state = 'B';
//这里需要唤醒所有的阻塞线程,然后它们再判断自己该不该打印
lock.notifyAll();
}
});
Thread t2 = new Thread(()->{
synchronized (lock){
while(state != 'B'){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("B");
//B 已经打印了,该打印 C
state = 'C';
//这里需要唤醒所有的阻塞线程,然后它们再判断自己该不该打印
lock.notifyAll();
}
});
Thread t3 = new Thread(()->{
synchronized (lock){
while(state != 'C'){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("C");
}
});
t1.start();
t2.start();
t3.start();
}
}
结果:
1.4 wait() 和 sleep() 的对比
相似之处:
wait
和sleep
都可以让当前线程进入非可运行状态。wait
和sleep
都可以指定暂停的时间,单位是毫秒。wait
和sleep
都可以被其他线程用interrupt()
中断,抛出InterruptedException
异常。
区别:
wait
是Object
类的实例方法,而sleep
是Thread
类的静态方法。wait
必须在同步块或同步方法中调用,而sleep
可以在任何地方调用。wait
会释放当前对象的锁,让其他线程可以获取该对象的锁,而sleep
不会释放任何锁。- 在中途唤醒的时候,
wait
可以不用抛出异常(调用notify
或notifyAll
);而sleep
会抛出异常(调用interrupt()
)。 wait
通常用于线程间的通信,而sleep
通常用于控制线程的执行时间。
转载自:https://juejin.cn/post/7250328942841364541