wait()方法和notify()方法

由于操作系统对线程的调度是随机执行的,且线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知。但是,有时候在实际开发中,我们希望合理的协调多个线程之间的先后执行顺序。在Java中,wait()方法和notify()方法就是解决该问题的。

1.wait()方法

线程饿死:线程饿死也叫线程饥饿,当多个线程同时竞争一把锁的时候,由于操作系统对线程的调度是随机的,所以当获取锁的线程释放锁之后,接下来哪个线程会拿到锁,这是不确定的。但是由于其他线程都属于在锁上阻塞等待,处于阻塞状态,而当前释放锁的线程处于就绪状态,这个线程还是有很大概率拿到该锁的。这样就会导致其他线程一直吃不到CPU资源,出现线程饿死的现象。

语法形式:

syncronized(锁对象){

锁对象.wait();

}

虽然我们无法干预调度器对于线程的调度,但是我们可以通过调用wait()方法,然后面的逻辑先执行,等后面的逻辑先执行完之后,当前面的逻辑收到通知,再继续执行。

wait()所作的事情

1.让调用wait方法的线程进行等待(将该线程放到等待队列中)

2.释放当前线程所持有的锁

3.满足一定条件被唤醒之后,线程再此重新获取该锁

当一个线程调用对象的wait方法时,该线程会释放该对象的监视器锁,并进入等待队列中等待被唤醒。

注意:由于使用wait()方法会释放锁,所以,再Java中,使用notify()方法之前,我们先加锁,要搭配syncronized使用。否则会报出一个非法锁状态的异常。

如下图

wait()和join()的区别

两个方法都是等,但是join方法必须要等待另一个线程全部执行完之后,代码才能继续走下去,但是wait方法不一定另一个线程全部执行完,只需要下面的逻辑执行到notify方法,代码就可以继续走下去。

syncronized不也是等待吗?

我们要知道,有时尽管写了syncronized,但是它不一定触发等待,因为我们不确定别的线程是否为加锁状态。如果其他线程没有处于加锁状态,那么该线程就直接获取到锁,直接加锁,这个过程中并没有等待。

wait方法的结束条件:

1.其他线程调用notify方法

2.其他线程调用该线程的interrupted方法,会导致wait抛出InterruptedException异常,导致wait被唤醒,同时也导致该线程结束。

3.wait方法也提供了带参数版本,来指定等待的时间。这时只要等待的时间到了,线程就会自动唤醒,不用notify来通知唤醒。

当代码执行到wait方法后,该线程会一直等待下去,那么我们肯定不能让该线程继续等待下去,这时,我们就要用到notify方法去唤醒线程了。

2.notify()方法

语法:

syncronized(锁对象){

锁对象.notify();

}

notify()方法是用来唤醒因为wait方法而阻塞的线程。

注意事项:

  1. notify()方法也要搭配syncronized()使用,这是Java中特殊规定的。

  2. 使用notify()之前,务必要确保先wait了,否则,notify()方法就没起到唤醒的作用,但是也不会有副作用(抛异常)

  3. 如果有多个线程在同一个锁对象上进行了wait,那么,notify()会随机唤醒多个线程中的一个线程。

  4. 代码执行到notify()方法后,当前线程并不会马上释放锁,而是要等到执行完notify()方法的线程将程序执行完之后才会释放锁,也就是退出同步代码块(syncronied修饰的代码块)之后才会释放对象锁。

  5. wait方法和notify方法锁对象要是一个锁对象,才会起作用

  6. 被唤醒的线程不会立即获得对象的监视器锁并继续执行,而是从等待队列中移出,进入与其他线程竞争锁的状态。只有当该线程获得了对象的监视器锁后,才能继续执行wait之后的代码。

例子:

java 复制代码
public class Demo9 {
    public static void main(String[] args) {
        Object locker=new Object();
        Thread t1=new Thread(()->{
            synchronized (locker){
                System.out.println("t1在wait之前");
                try {
                    System.out.println("t1执行到wait,释放锁");
                    locker.wait();
                    System.out.println("t1被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2=new Thread(()->{
            synchronized (locker){
                System.out.println("t2获取到锁");
                try {
                    locker.wait();
                    System.out.println("t2被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t3=new Thread(()->{
            synchronized (locker){
                System.out.println("t3唤醒t1");
                locker.notify();
                System.out.println("同步代码块执行完,t3释放锁");
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

根据代码运行结果分析代码逻辑:线程t1启动并获取到锁,进行加锁,接着线程t2也启动,也尝试获取锁,由于t1没有释放锁,t2就阻塞了。接着在线程t1中,执行到wait()方法,t1此时就释放锁,然后线程t3就获取到了锁,当在线程t3中执行到notify()方法,去唤醒t1,当t3中的同步代码块里面的逻辑执行完后,就释放锁 ,由于t1被唤醒需要时间在这段时间内,由于t1没被唤醒,无法获取到锁,线程t2就获取到了锁,线程t2就会执行到wait,wait方法就会导致线程t2释放锁,这回t1就已经被唤醒了,就会获取到锁,线程t1就会继续执行下去。但是线程t2由于没有notify方法去唤醒,所以它就一直处于睡眠状态,不会被唤醒。

2.1notifyAll()

如果我们想要一次唤醒多个线程在同一个锁对象进行wait,我们就可以使用notifyAll()方法。

如修改上面代码,将线程t1和线程t2全部唤醒。

java 复制代码
    public static void main(String[] args) {
        Object locker=new Object();
        Thread t1=new Thread(()->{
            synchronized (locker){
                System.out.println("t1在wait之前");
                try {
                    locker.wait();
                    System.out.println("t1被notifyAll()唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2=new Thread(()->{
            synchronized (locker){
                System.out.println("t2在wait之前");
                try {
                    locker.wait();
                    System.out.println("t2被notifyAll()唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t3=new Thread(()->{
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入唤醒所有线程");
            scanner.next();
            synchronized (locker){
                locker.notifyAll();
                System.out.println("notifyall方法后");
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }

注意事项:notifyAll()方法在唤醒所有在等待队列中的线程时,这几个线程是存在锁竞争的,只有一个线程能获得锁,其余线程则会继续阻塞,继续尝试获取锁。

3.wait()和sleep()的区别

1.wait方法需要搭配锁来使用,先加锁,之后才能wait,而sleep使用前不需要加锁。

2.如果都是在syncronized内部使用,wait会释放锁,而sleep方法不会释放锁。

相关推荐
小江的记录本14 分钟前
【JVM虚拟机】堆内存分代模型:年轻代(Eden+Survivor)、老年代、元空间Metaspace(附《思维导图》+《面试高频考点清单》)
java·前端·jvm·后端·python·spring·面试
在繁华处18 分钟前
Java从零到熟练(三):流程控制
java·开发语言·python
唐青枫41 分钟前
Java Optional 实战指南:优雅处理空值与链式转换
java
一起学开源43 分钟前
一文读懂 ReAct 范式:让 AI Agent 真正学会“思考+行动“
java·javascript·react.js·ecmascript·react·alibaba·智能体开发
云泽8081 小时前
C++ 可调用对象通关指南:深度解析 Lambda 表达式、function 包装器与 bind 绑定器
开发语言·c++·算法
逍遥德2 小时前
MQTT教程详解-04.SpringBoot集成MQTT(告别手动控制)
java·spring boot·物联网·中间件·iot·iotdb
语戚2 小时前
力扣 3161. 块放置查询:线段树解法(Java 实现)
java·算法·leetcode·面试·线段树·力扣·
我命由我123453 小时前
Android 开发问题:MlKitException: An internal error occurred during initialization.
android·java·java-ee·android jetpack·android-studio·androidx·android runtime
星恒随风3 小时前
Python 基础语法详解(一):从表达式、变量到数据类型
开发语言·笔记·python·学习
888CC++3 小时前
java 并发编程
java·开发语言·python