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模式

此模式下,分三步:

  1. step1:获得对象的锁;
  2. step2:循环判断是否需要进行生产活动,如果不需要就调用wait(),暂停当前线程,如果需要进行生产活动,进行对应的生产活动;
  3. 通知等待线程;
    1. synchronized(对象) {
    2. //这边进行循环判断的原因是为了防止伪唤醒,也就是不是消费线程或者生产线程调用notify方法将waiting线程唤醒的
    3. while(条件){
    4. 对象.wait();
    5. }
    6. //进行生产或者消费活动
    7. doSomething();
    8. 对象.notifyAll();
    9. }

5、wait()和sleep()的异同

相同点:二者都能暂停线程的执行;
不同点:

  • wait()声明在Object类中,而sleep()声明在Thread类中;
  • wait()只能在同步代码块或者同步方法中使用,sleep()可以在任何需要的场景下使用;
  • wait()会释放锁,而sleep()不会;所以sleep()通常用于暂停线程执行,而wait()通常用于线程之间的互相通信;
  • wait()调用之后不会自动苏醒,需要其他线程调用同一个对象的notify()或者notifyAll()方法;wait(long time)超时后线程会自动苏醒;而sleep()执行后,线程会自动苏醒。

6、使用示例

  1. package com.yuanhai.java2;
  2. /**
  3. * 本类说明:
  4. * 线程通信案例:
  5. * 使用两个线程打印1-100,线程1线程2交替打印
  6. *
  7. * 涉及到的三个方法
  8. * wait(): 一旦执行此方法,当前线程就进入阻塞状态,并释放锁
  9. * notify(): 一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的线程。
  10. * notifyAll(): 一旦执行此方法,就会唤醒所有被wait的线程。
  11. *
  12. * 说明:
  13. * 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。(因此Lock方式的同步处理不可以使用)
  14. * 2.wait(),notify(),notifyAll()三个方法的调用者,必须是同步代码块或同步方法中的同步监视器(即:锁)。
  15. * 否则,会出现IllegalMonitorStateException异常。
  16. * 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中
  17. *
  18. * 面试题:sleep()和wait()的异同
  19. * 同:一旦执行sleep()或wait(),都可以使当前线程进入阻塞状态
  20. * 异:1)两个方法声明的位置不同:sleep()声明在Thread类中;
  21. * wait()声明在Object类中;
  22. * 2)调用的要求不同:sleep()可以在任何需要的场景下调用;
  23. * wait()不只使用在同步代码块或同步方z法中;
  24. * 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁;
  25. *
  26. *
  27. *
  28. * @author yuanhai
  29. * @date 2021年11月22日
  30. */
  31. class Number implements Runnable{
  32. private int number = 1;
  33. @Override
  34. public void run() {
  35. while (true){
  36. synchronized (this) {
  37. // 2.唤醒阻塞线程
  38. notify();
  39. if(number <= 100){
  40. System.out.println(Thread.currentThread().getName()+":"+number);
  41. number++;
  42. try {
  43. // 1.使调用如下wait()方法的线程进入阻塞状态
  44. // 执行wait()方法会释放锁,而sleep()不会释放锁
  45. wait();
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. }else{
  50. break;
  51. }
  52. }
  53. }
  54. }
  55. }
  56. public class CommunicationTest {
  57. public static void main(String[] args) {
  58. Number number = new Number();
  59. Thread t1 = new Thread(number);
  60. Thread t2 = new Thread(number);
  61. t1.setName("线程1");
  62. t2.setName("线程2");
  63. t1.start();
  64. t2.start();
  65. }
  66. }