Skip to content

线程通信

当我们需要多个线程共同完成一个任务时,并且希望线程之间能够有规律的执行,那么线程之间就需要进行通信,来协调它们工作。

例如:线程A负责生产包子,线程B负责吃包子,包子可以理解为共同操作的资源,线程B只能等线程A制作好包子之后才能吃,线程A只能等线程B要吃包子了才能生产。

这就是 线程的等待唤醒机制。

等待唤醒机制

在线程满足某个条件时,就进入 等待状态 wait() / wait(time) ,等待其他线程执行完代码释放同步监视器之后再将其 唤醒 notify()

在有多个线程进行等待时,如果需要,可以使用 notifyAll() 来唤醒所有的等待线程

方法名称描述
wait等待线程执行该方法,就进入等待状态,同时释放锁
notify唤醒线程执行该方法,就会唤醒被 wait 的线程(当有多个线程被 wait 时,唤醒优先级最高的线程,优先级相同则随机唤醒)
notifyAll唤醒所有等待线程执行该方法,会唤醒所有被 wait 的线程
java
public static void main(String[] args) {
  PrintfNumber p1 = new PrintfNumber();

  Thread t1 = new Thread(p1, "线程一");
  Thread t2 = new Thread(p1, "线程二");

  t1.start();
  t2.start();
}

class PrintfNumber extends Thread {
  private static int number = 0;

  @Override
  public void run() {
    while (true) {
      synchronized (this) { //使用 this 作为同步监视器,因为当前类只被创建了一次
        notify();
        //this.notify();

        if (number <= 100) {
          System.out.println(Thread.currentThread().getName() + ":" + number);
          number++;
        } else {
          break;
        }

        try {
          wait();
          //this.wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

上述代码中,线程一进入同步代码块打印后,到达 wait() 方法处等待,然后线程二进入同步代码块,首先把 wait 的线程一唤醒,然后线程二又到达 wait() 处等待,二者相互交替打印数值。

注意

  1. wait()、notify()、notifyAll() 三个方法只能在同步代码块或同步方法中使用,不能在 ReentrantLock锁 中使用

  2. 在同步代码块或同步方法中使用时,一定要 注意同步监视器是谁,因为这三个方法只能由同步监视器来调用,例如上面的示例,wait 和 notify 其实省略了 this 同步监视器来调用;

    java
    class PrintfNumber extends Thread {
      Object obj = new Object();
    
      @Override
      public void run() {
        synchronized (obj) { //使用 obj 作为同步监视器,wait和notify就必须由 obj 来调用
          obj.notify();
    
          //...
    
          try {
            obj.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }
面试题:sleep() 方法和 wait() 方法相同点和不同点?

相同点:

  1. 二者都可以导致线程阻塞;

不同点:

  1. 声明的对象不同:wait() 声明在 Object 类中,因此可以通过 obj.wait() 调用,而 sleep() 声明在 Thread 类中;
  2. 使用场景不同:wait() 只能使用在同步代码块或同步方法中,而 sleep() 可以声明在任何地方;
  3. 能否释放同步监视器:wait() 会释放同步监视器,而 sleep() 不会释放;
  4. 结束阻塞的方式:wait() 阻塞之后,只能通过 notify() / notifyAll() 唤醒,而 sleep() 是计时结束后结束阻塞;

Released under the MIT License.