1、wait()方法
wait()方法是Object类下的方法,它必须在synchronized修饰的方法或者代码块中调用,否则,编译不会报错,但是运行会出现IllegalMonitorStateException,非法的监视器状态异常。
wait()方法相关知识点:
- 当一个线程调用一个共享变量对象的wait()时,当前线程会进入阻塞状态,直到被唤醒或者被中断才能返回:
- 被唤醒:其他线程调用了该共享变量对象的notify()/notifyAll()方法;这时当前线程会回到就绪状态;
- 被打断:其他线程调用了该线程的interrupt(),该线程抛出了InterruptedException异常,线程中止;
- 如果调用wait()的线程没有实现获取该对象的监视器锁,则调用wait()时,调用线程会抛出IllegalMonitorStateException异常。如果当前线程已经获取了锁,调用wait()之后会释放锁,但只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,其他锁不会被释放;
- wait()有个重载方法wait(long time),这个方法会等待传入的time时间,如果这个时间内,没有其他线程来唤醒它,那么这个线程会自己唤醒,继续获得执行几机会。
2、 notify()方法
notify()方法也是Object类下的方法,它必须在synchronized修饰的方法或者代码块中调用,否则,编译不会报错,但是运行会出现IllegalMonitorStateException,非法的监视器状态异常。
- 调用此方法,会唤醒等待对象监视器(锁)的单个线程,如果等到锁的有多个线程,那么会选取其中一个线程进行唤醒,但是到底唤醒哪个线程是任意的,由CPU决定(唤醒优先级最高的);
- 北唤醒的线程不会马上从wait()方法返回并继续执行,而是必须要在获取了共享对象的监视器锁之后才可以返回。也就是唤醒它的线程释放了共享变量上的监视器后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为此时该线程需要和其他线程一起竞争锁,只有该线程竞争到了锁后才可以继续执行;
- 只有当前线程获取到了共享变量的监视器锁之后,才可以调用共享变了的notify()方法,否则会抛出IllegalMonitorStateException;也就是notify必须在synchronized修饰的方法或者代码块中调用。
3、 notifyAll()方法
notifyAll的使用同notify()一样,必须在synchronized修饰的方法或者代码块中调用,否则,编译不会报错,但是运行会出现IllegalMonitorStateException,非法的监视器状态异常
- 它会唤醒所有等待锁(monitor监视器对象)的线程;
- 在共享变量上调用notifyAll()方法,只会唤醒调用这个方法前调用了wait系列函数而被放入共享变量等待集合里面的线程;
- 如果调用notifyAll()之后一个线程调用了该共享变量的wait()方法而被放入阻塞集合,该线程不会被唤醒。
4、 wait-nofity模式
此模式下,分三步:
- step1:获得对象的锁;
- step2:循环判断是否需要进行生产活动,如果不需要就调用wait(),暂停当前线程,如果需要进行生产活动,进行对应的生产活动;
- 通知等待线程;
synchronized(对象) {
//这边进行循环判断的原因是为了防止伪唤醒,也就是不是消费线程或者生产线程调用notify方法将waiting线程唤醒的
while(条件){
对象.wait();
}
//进行生产或者消费活动
doSomething();
对象.notifyAll();
}
5、wait()和sleep()的异同
相同点:二者都能暂停线程的执行;
不同点:
- wait()声明在Object类中,而sleep()声明在Thread类中;
- wait()只能在同步代码块或者同步方法中使用,sleep()可以在任何需要的场景下使用;
- wait()会释放锁,而sleep()不会;所以sleep()通常用于暂停线程执行,而wait()通常用于线程之间的互相通信;
- wait()调用之后不会自动苏醒,需要其他线程调用同一个对象的notify()或者notifyAll()方法;wait(long time)超时后线程会自动苏醒;而sleep()执行后,线程会自动苏醒。
6、使用示例
package com.yuanhai.java2;
/**
* 本类说明:
* 线程通信案例:
* 使用两个线程打印1-100,线程1线程2交替打印
*
* 涉及到的三个方法
* wait(): 一旦执行此方法,当前线程就进入阻塞状态,并释放锁
* notify(): 一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的线程。
* notifyAll(): 一旦执行此方法,就会唤醒所有被wait的线程。
*
* 说明:
* 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。(因此Lock方式的同步处理不可以使用)
* 2.wait(),notify(),notifyAll()三个方法的调用者,必须是同步代码块或同步方法中的同步监视器(即:锁)。
* 否则,会出现IllegalMonitorStateException异常。
* 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中
*
* 面试题:sleep()和wait()的异同
* 同:一旦执行sleep()或wait(),都可以使当前线程进入阻塞状态
* 异:1)两个方法声明的位置不同:sleep()声明在Thread类中;
* wait()声明在Object类中;
* 2)调用的要求不同:sleep()可以在任何需要的场景下调用;
* wait()不只使用在同步代码块或同步方z法中;
* 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁;
*
*
*
* @author yuanhai
* @date 2021年11月22日
*/
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this) {
// 2.唤醒阻塞线程
notify();
if(number <= 100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
// 1.使调用如下wait()方法的线程进入阻塞状态
// 执行wait()方法会释放锁,而sleep()不会释放锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}