wait 和 notify

x33g5p2x  于2021-12-05 转载在 其他  
字(2.4k)|赞(0)|评价(0)|浏览(238)

由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序

wait( )方法

wait 方法做的事

  • 使当前执行代码的线程进行等待,(把线程放到等待队列中)
  • 释放当前的锁
  • 满足一定条件时被唤醒,重新尝试获取这个锁

代码示例:

public class ThreadDemo19 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        System.out.println("等待前");
        object.wait();
        System.out.println("等待后");
    }
}

预计执行结果:
此代码中,由于没有进行任何的通知机制;所以,预期效果,是一直去等待

实际执行结果:

synchronized — 监视器锁
wait 要搭配 synchronized 来使用,脱离 synchronized 使用 wait 会直接抛出异常

wait 的工作过程:
1.释放锁 (得先有一个锁,才能释放)
2.等待通知 (这个过程可能很久)
3.当收到通知后,尝试重新获取锁,继续往下执行

修改之后的代码:

public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    synchronized (object){
        System.out.println("等待前");
        object.wait();
        System.out.println("等待后");
    }
}

此时运行程序,就会陷入阻塞,会持续多久,不好说

可以通过 jconsole 窗口来查看:

竞态条件问题

wait 方法,的执行过程分为三步,下面线程1 调用wait 方法
画图表示:

如何避免 竞态条件问题??
事实上,操作1 和 操作2 在wait 上是原子的
也就是说,只要调用 wait,1 和 2 是一气呵成的,不会先后执行~

wait 结束等待的条件

  • 其他线程调用该对象的 notify 方法
  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本,来指定等待时间)
  • 其他线程调用该等待线程的 interrupted 方法,导致 wait 抛出 InterruptedException 异常

wait 和 sleep 的对比?

  • sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁(如果有的话)。
  • wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠时,会释放互斥锁。
  • sleep 方法没有释放锁,而 wait 方法释放了锁 。
  • sleep 通常被用于暂停执行,wait 通常被用于线程间交互/通信
  • sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法

notify( )方法

notify 方法是唤醒等待的线程

  • 方法notify( ) 也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的
    其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程. (并没有 “先来后到”)
  • 在notify( ) 方法后,当前线程不会马上释放该对象锁,要等到执行notify( ) 方法的线程将程序执行
    完,也就是退出同步代码块之后才会释放对象锁

代码示例:

public class ThreadDemo20 {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();

        Thread t1 = new Thread(){
            @Override
            public void run(){
                synchronized (locker){
                    while (true){
                        try {
                            System.out.println("wait 开始");
                            locker.wait();  // 要和 synchronized 对应的对象对应
                            System.out.println("wait 结束");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        };
        t1.start();

        Thread t2 = new Thread(){
            @Override
            public void run(){
                Scanner scan = new Scanner(System.in);
                System.out.println("输入任意一个整数, 继续执行notify() ");
                int num = scan.nextInt();
                synchronized (locker){
                    System.out.println("notify 开始");
                    locker.notify();  // notify 的对象和 wait 的对象要对应,才有效果
                    System.out.println("notify 结束");
                }
            }
        };
        t2.start();
    }
}

执行结果:

画图分析执行过程:

notifyAll( )方法 (不建议使用)

notify 方法只是唤醒某一个等待线程,使用 notifyAll 方法可以一次唤醒所有的等待线程,这些线程再去竞争同一把锁

相关文章