目录

1.wait 和 notify 引入

2.whit() 方法

3.notify() 方法

4.wait() 和 notify() 注意点

5.wait() 和 join()、sleep() 的区别

5.1 wait() 和 join() 的区别

5.2 wait() 和 join() 的区别

5.3 wait() 和 join()、sleep() 的区别汇总:


1.wait 和 notify 引入

在学习多线程,我们多次强调线程是随机调度、抢占式执行的,为此我们就针对这句话学习了很多关于多线程的知识。针对线程等待引入 join() 方法、线程睡眠引入 sleep() 方法,线程安全原子性问题引入关键字 synchronized ,线程安全可见性问题和指令重排序问题引入关键字 volatile 等等。可是不论是方法也好,关键字也罢,因为这些线程是随机调度、抢占式执行的,导致它们都有一个缺点,就是当第一个线程执行后,第二个线程就无法再次执行,只能阻塞等待,直到第一个线程执行完毕。如果现在就这么一个需求:第一个线程执行一段时间,但是并没有执行结束,此时它就想“摆烂”一会,让第二个线程先执行工作,等再过一段时间,第一个线程再执行,怎么解决这个办法呢?

比如:你进一家餐馆就餐,你要通知服务员进行点单,你点完单后就要等待,而这时候服务员就要通知后厨,等餐点准备好后,服务员会通知你,你的餐准备好了,这个时候你就可以就餐了。

很显然,对于这样的需求,根据过往所学的多线程知识是解决不了的。因为根据过往的多线程知识,“你”就相当于一个线程,想让“服务员”这个线程执行,那么就说明“你”这个线程已经执行结束了,虽然现实生活你肯定会等到餐点,但是计算机的线程已经执行完毕,start() 只开启一次,执行结束就不再可以执行。

所以,针对这类问题,Java中提供了等待方法 wait()唤醒方法 notify()这两个方法都属于 Object 类,并且要搭配锁使用。这些方法在使用时被这个锁调用使用时也会抛出 InterruptedException,所以要事先声明。

2.whit() 方法

wait():让当前线程进入等待状态,使用时要在同步方法块(synchronized)内使用,否则会抛出 IllegalMonitorStateException 异常,并且要求 wait 锁对象和 synchronized 锁对象一致。

先来看一段代码:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        synchronized (locker) {
            System.out.println("wait 之前...");
            locker.wait();
            System.out.println("wait 之后...");
        }
    }
}

代码中定义了一个锁,并且在锁中调用 wait() 方法,如果运行这个代码,会一直等待,怎么证明?我们借助 jconsole 查看状态

当前代码运行的是在 main 线程,可以看到该线程状态是 WAITING 。

但是如果一个线程没有另一个线程来唤醒时,是不是意味着一直阻塞等待,导致死等?所以在这里 wait() 方法也提供了带参数的版本,即 wait(时间) 单位是 ms超过这个时间还没有其它线程来唤醒时,会继续往下执行。这里不再进行演示。

wait 要做的事情就是:

  • 执行当前代码的线程进入等待状态
  • 释放当前拥有的锁
  • 满足一定条件时被唤醒,再次尝试获取锁

wait 等待结束的条件:

  • 有其他线程调用该对象的 notify() 方法
  • 如果 wait() 方法带有时间参数,那么时间超时也会结束等待
  • 其它线程调用该线程的 interrupted 方法,相当于中断这个线程 

3.notify() 方法

notify():唤醒当前对象的等待线程,使用时也要在同步方法块(synchronized)内使用,否则会抛出 InterruptedException 异常。如果有多个线程在等待当前线程唤醒,根据线程随机调度会随机唤醒一个正在 WAITING 的线程。notify() 持有的锁对象要和 wait() 一致。

这里来定义三个线程,让两个线程进入 WAITING 状态,一个线程来唤醒,感受一下 :

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                try {
                    System.out.println("线程1wait之前...");
                    locker.wait();//进入WAITING状态
                    System.out.println("线程1wait之后...");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        },"等待线程1");

        Thread t2 = new Thread(() -> {
            synchronized (locker) {
                try {
                    System.out.println("线程2wait之前...");
                    locker.wait();//进入WAITING状态
                    System.out.println("线程2wait之后...");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        },"等待线程2");

        Thread t3 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("唤醒线程notify之前");
                locker.notify();
                System.out.println("唤醒线程notify之后");
            }
        },"唤醒线程");
        //开启线程
        t1.start();
        t2.start();
        Thread.sleep(1000);//为了更好地看到现象这里休眠1秒,目的先让等待线程执行
        t3.start();
    }
}

代码解读:wait() 和 notify() 都要被同一个锁对象调用,并且两个等待线程中的锁对象也是同一个,这样当唤醒线程执行后,会随机唤醒一个等待线程。

运行结果:

结果分析:线程1比线程2先执行,这是由于线程的随机调度导致,等待1秒后,唤醒线程执行,从结果来看,此时是唤醒线程1,因为打印了“线程1wait 之后”,线程2发生阻塞等待,进入WAITING状态,在这里同样借助 jconsole 查看状态

可以看到,在线程中,等待线程2依旧处于WAITING状态,而等待线程1、唤醒线程和main线程已经执行完毕,所以并没有看到这两个线程。

有没有声明办法一次唤醒所有线程呢?肯定是有的,那就是 notifyAll() 方法,上述代码中,只要把 notify() 方法改成 notifyAll() 就可以解决,使用这个方法,持有当前锁对象正在 WAITING 的线程由于随机调度也会随机执行,比如(下面的输出结果是把 notify() 方法换成了 notifyAll(),其它的都没有改变):

等待之前是线程1先执行,而唤醒之后是线程2先执行,这又相当于对线程的随机调度、抢占式执行的再次理解!

4.wait() 和 notify() 注意点

关于wait() 和 notify() 注意点在上面介绍都有讲到,但是这里也统一罗列出来:

  1. wait() 和 notify() 属于Object类
  2. wait() 必须满足锁状态,这个方法需要释放锁,并且 wait() 要在 notify() 之前,这样正在 WAITING 状态的线程才会被唤醒,如果 notify() 在 wait() 之前,虽然没有副作用(不会抛异常之类的),但是会导致后面 WAITING 的线程一致等待
  3. wait(), notify() 和 synchronized 的锁对象必须一致,在使用时要注意区分
  4. 一次 notify 只能唤醒一个 wait 对象,这是因为线程的随机调度、抢占式执行导致
  5. 调用 wait() 时,相当于相当于获取锁状态,调用 notify() 后,因为是在 synchronized内,所以这个线程不会立即结束,而是等待唤醒线程即将结束后才会解锁(这个期间相当于等待线程在抢占锁),此时 wait() 解锁,继续执行 wait() 之后的代码

5.wait() 和 join()、sleep() 的区别

5.1 wait() 和 join() 的区别

wait() 和 join() 都表示等待的意思,并且也提供来“死等”版本和“超时”版本“。它们有什么区别呢?

(1)join() 的底层实现是 wait

这里可以打开 join() 方法的源码查看:

(2)wait() 所属类是 Object 类,join() 所属类是 Thread 类

(3)wait() 可以被 notify() 唤醒,也可以等待超时后执行当前等待的线程,join()需要等待上一个线程执行结束或超时,才能被唤醒执行下一个线程

(4)wait() 必须搭配锁使用,代码要在 synchronized 代码块内,并且 wait() 和 notify() 持有的锁对象要一致,join() 的底层是 wait() ,内部通过 wait() 释放锁(这就是为什么源码 join() 方法被 synchronized 修饰的原因),而不会直接暴露锁机制

5.2 wait() 和 join() 的区别

wait() 引入带有时间参数的版本后,和 sleep() 非常像,都可以表示等待一段的意思。它们有什么区别呢?

(1)wait() 所属类是 Object 类,不是静态方法,sleep() 所属类是 Thread 类,是静态方法,调用方式是 Thread.sleep(时间)

(2)wait() 可以被 notify() 唤醒,也可以等待超时后执行当前等待的线程,sleep() 会让当前线程短暂睡眠指定时间,时间到期后才唤醒

(3)wait() 必须搭配锁使用,代码要在 synchronized 代码块内,并且 wait() 和 notify() 持有的锁对象要一致,sleep() 不需要搭配锁

(4)wait() 位置必须在 synchronized 代码块内,sleep() 可以是线程内的任意位置

5.3 wait() 和 join()、sleep() 的区别汇总:


关于多线程的基础知识学了这么久,虽然有结合生活案例来理解,但是多线程在具体实际中的应用模式是怎么样的呢?使用多线程怎么考虑线程安全?

欲知后事如何,且听下回分解!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐