目录
1.什么是线程的等待通知
2.wiat()方法
2.1 wait 做的事情:
2.2wait 结束等待的条件:
代码示例:
2.3wait的三种重载方式
2.4 面试问题:wait()和sleep()之间的区别
3.notify()方法
3.1notify ⽅法是唤醒等待的线程.
3.2wait 和notify之间的联系
3.3notifyAll()
线程的等待通知是多线程编程中常用的一种机制,用于线程之间的协作和同步。在Java中,线程的等待通知通过使用wait()
、notify()
和notifyAll()
方法来实现。
由于线程之间是抢占式执⾏的, 因此线程之间执⾏的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执⾏先后顺序,所以我们需要wait()、notify()来实现这些功能。
• 使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中)• 释放当前的锁• 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁.
• 其他线程调⽤该对象的 notify ⽅法.• wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).• 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常.
package 多线程;
public class ThreadDemo18 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();//new一个对象
synchronized (object){//对对象进行加锁
System.out.println("等待中···");
object.wait();//当前线程会释放对象的锁,并进入等待状态。
System.out.println("等待结束。");
}
}
}
主线程会打印"等待中···",然后调用object.wait()
方法,释放对象的锁,并进入等待状态。当其他线程调用了object.notify()
或object.notifyAll()
方法时,主线程会被唤醒,然后重新获取对象的锁,并继续执行输出"等待结束。"。但是这个代码种没有notify()方法,所以没有重新唤起线程,就没有输出等待结束。
第一种wait()重载方式:它是死等的,就是说如果没有notify()方法来唤醒它,它就一直处于阻塞状态。
第二种wait()重载方式:它自己设定了一个超时的时间,单位是ms。就是说它最多等待到设定的时间,在这个时间内没有notify也不等了,直接会被唤醒。
第三种wait()重载方式:方法使当前线程进入等待状态,直到其他线程调用该对象notify()
、notifyAll()
方法唤醒它,或者指定的超时时间到达。这个方法允许设置纳秒级别的超时时间,除了毫秒级别的参数外,还可以指定纳秒级别的增量。
wait提供了一个带超时间的版本
sleep也是可以指定时间 都是时间到了解除阻塞。
wait和sleep都是可以提前被唤醒的
wait通过notify()来唤醒
sleep通过interrupt来唤醒
使用wait的主要目的,一定是不知道要等多少时间的前提下来使用的。所谓的超时间,其实是“兜底的”。
使用sleep,一定是知道要等多长时间,必须要等到那个时间才会被唤醒。
• ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其 它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。• 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到")• 在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏ 完,也就是退出同步代码块之后才会释放对象锁。
代码示例:
package 多线程;
public class ThreadDemo19 {
public static void main(String[] args) {
//需要一个统一的对象进行加锁,wait,notify
Object locker = new Object();
Thread t1 = new Thread(()->{
synchronized (locker){
System.out.println("t1 wait 之前");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 wait 之后");
}
});
Thread t2 = new Thread(()->{
try {
Thread.sleep(5000);
synchronized (locker){
System.out.println("t2 notify 之前");
locker.notify();
System.out.println("t2 notify 之后");
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
1) t1 执行起来之后,就会先立即拿到锁,并且打印t1 wait 之前,并进入wait 方法(释放锁+阻塞等待)
2) t2 执行起来之后,先进行 sleep(5000)(这个 sleep 就可以让t1 能够先拿到锁)
3) t2 sleep 结束之后,由于t1 是wait 状态,锁是释放的.t2 就能拿到锁
接下来打印t2 notify 之前,执行 notify 操作,这个操作就能唤醒t1.(此时t1 就从 WAITING状态恢复回来了)
4)但是由于 t2 此时还没有释放锁呢,t1 WAITING 恢复之后,尝试获取锁,就可能出现一个小小的阻塞.
5) t2执行完 t2-motify 之后,释放锁,t2执行完毕.
所以输出结果如下:
wait 和notify之间是通过Object对象联系起来的。
Object1.wait()
Object2.notify() 此时如果用notify是无法被唤醒的,必须是两个对象一样才能唤醒。
Object1.wait()
Object2.wait()此时notify使用的哪个对象,哪个对象才能被唤醒。
唤醒这个对象上所有等待的线程.假设有很多个线程,都使用同一个对象 wait.针对这个对象进行 notifyAII,此时就会全都唤醒~~但是注意,这些线程在wait返回的时候,要重新获取锁,就会因为锁的竞争,使这些线程实际上是一个一个串行执行的.
具体使用方式如下:
notifyAll()
方法必须在同步代码块或同步方法中调用,并且只能应用于被synchronized
关键字修饰的对象上。当调用对象的
notifyAll()
方法时,该对象上所有等待的线程都会被唤醒,并尝试重新获取对象的锁。被唤醒的线程会进入就绪状态,然后根据线程调度机制竞争获取锁。
只有获取到对象的锁的线程才能继续执行同步代码块中的内容,而其他未获取到锁的线程仍然处于阻塞状态,直到再次获得锁的机会。