C语言学习网

怎么在Java多线程中使用wait、notify、notifyAll

发表于:2022-12-08 作者:安全数据网编辑
编辑最后更新 2022年12月08日,本篇文章给大家分享的是有关怎么在Java多线程中使用wait、notify、notifyAll,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看

本篇文章给大家分享的是有关怎么在Java多线程中使用wait、notify、notifyAll,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。

基础知识

首先我们需要知道,这几个都是Object对象的方法。换言之,Java中所有的对象都有这些方法。

public final native void notify();public final native void notifyAll();public final native void wait(long timeout) throws InterruptedException;public final void wait() throws InterruptedException {  wait(0);}

其中notify()、notifyAll()、wait(long timeout)是本地方法

其次我们需要知道这几个方法主要是用来个线程之间通信的。那可能就有人会问,既然是用来线程之间通信的,那为什么这几个方法不是在线程类Thread上呢?对于这个问题,我们先来看下这几个方法的具体使用方式再来回答这个问题。

用法

Java中规定,在调用者三个方法时,当前线程必须获得对象锁。因此就得配合synchronized关键字来使用

//使用模式,不代表可运行代码synchronized(object) {  while(contidion) {    object.wait();  }  //object.notify();  //object.notifyAll();}

或者

public synchronized void methodName() {  while(contidion) {    object.wait();  }  //object.notify();  //object.notifyAll();}

在synchronized拿到对象锁之后,synchronized代码块或者方法中,必定是会持有对象锁的,因此就可以使用wait()或者notify()。

通过上述使用方法,我们也能很好理解为什么这几个方法是在Object上而不是在Thread上。因为每个对象都可以作为synchronized锁的对象,因此wait、notify等必须和对象关联才能配合synchronized使用。

作用

方法作用
wait线程自动释放占有的对象锁,并等待notify。
notify随机唤醒一个正在wait当前对象的线程,并让被唤醒的线程拿到对象锁
notifyAll唤醒所有正在wait当前对象的线程,但是被唤醒的线程会再次去竞争对象锁。因为一次只有一个线程能拿到锁,所有其他没有拿到锁的线程会被阻塞。推荐使用。

1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。

2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。

3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。

当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。

只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。

也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程

4、wait() 需要被try catch包围,中断也可以使wait等待的线程唤醒。

5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

6、notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

7、在多线程中要测试某个条件的变化,使用if 还是while?

要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑,显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while来执行,以确保条件满足和一定执行。如下代码:

public class K {  //状态锁  private Object lock;  //条件变量  private int now,need;  public void produce(int num){    //同步    synchronized (lock){      //当前有的不满足需要,进行等待      while(now < need){        try {          //等待阻塞          wait();        } catch (InterruptedException e) {          e.printStackTrace();        }        System.out.println("我被唤醒了!");      }      // 做其他的事情    }  }}

显然,只有当前值满足需要值的时候,线程才可以往下执行,所以,必须使用while 循环阻塞。注意,wait() 当被唤醒时候,只是让while循环继续往下走.如果此处用if的话,意味着if继续往下走,会跳出if语句块。但是,notifyAll 只是负责唤醒线程,并不保证条件云云,所以需要手动来保证程序的逻辑。

实际案例

接下来我们就使用wait()、notify()来实现一个生产者、消费者模式。这个也是面试过程中可能会被问到的地方。至于什么是生产者消费者模式,不明白的同学请自行百度。

首先是一些基础的代码

private static Boolean run = true;//控制是否生产和消费private static final Integer MAX_CAPACITY = 5;//缓冲区最大数量private static final LinkedBlockingQueue queue = new LinkedBlockingQueue<>();//缓冲队列

生产者代码

/** * 生产者 */class Producter extends Thread {  @Override  public void run() {    while (run) {      synchronized (queue) {        while (queue.size() >= MAX_CAPACITY * 2) {          try {            System.out.println("缓冲队列已满,等待消费");            queue.wait();          } catch (InterruptedException e) {            e.printStackTrace();          }        }        try {          String string = UUID.randomUUID().toString();          queue.put(string);          System.out.println("生产:" + string);          Thread.sleep(500);        } catch (InterruptedException e) {          e.printStackTrace();        }        queue.notifyAll();//通知生产者和消费者      }    }  }}

消费者代码

/** * 消费者 */class Consumer extends Thread {  @Override  public void run() {    while (run) {      synchronized (queue) {        while (queue.isEmpty()) {          try {            System.out.println("队列为空,等待生产");            queue.wait();          } catch (InterruptedException e) {            e.printStackTrace();          }        }        try {          System.out.println("消费:" + queue.take());          Thread.sleep(500);        } catch (InterruptedException e) {          e.printStackTrace();        }        queue.notifyAll();//通知生产者和消费者      }    }  }}

代码说明

1、生产者和消费者都继承了线程Thread,因为wait、notify本身就是线程间通信使用

2、生产者和消费者都有两层while,外层的while是用来判断是否运行生产者和消费者。内存的while用来判断队列queue是否已满或者为空,如果满足条件,则使得当前线程变成等待状态(等待notify)。

3、内层的条件判断为什么用while不用if,原因是当线程被wait之后,会释放对象锁。当等待的线程被notify之后,必须再次尝试去获取对象锁,如果没有获取到对象锁,那还必须等待,直到拿到对象锁之后才能向后执行。

4、当生产者生产了一个数据或者消费者消费了一个数据之后,使用notifyAll()方法来通知所有等待当前对象锁的线程,但是一次只会有一个等待的线程能拿到锁。

5、我们使用queue作为锁的对象在不同线程之间进行通信

代码运行结果

队列为空,等待生产
生产:e422484e-8eb3-4a91-8a7c-97e21d7ef498
生产:7894b802-2529-4798-ba98-9f0657f39240
生产:848f6759-a427-4a94-89dc-3f484daaa467
生产:f711d3dc-972c-4c44-8640-faffe376d354
生产:38a08e62-d774-4ed5-8b51-f1ad534c246f
消费:e422484e-8eb3-4a91-8a7c-97e21d7ef498
消费:7894b802-2529-4798-ba98-9f0657f39240
消费:848f6759-a427-4a94-89dc-3f484daaa467
消费:f711d3dc-972c-4c44-8640-faffe376d354
消费:38a08e62-d774-4ed5-8b51-f1ad534c246f
队列为空,等待生产
生产:9fae26f3-0b6e-4fbd-9620-040667efe0af
生产:95bb1d88-e08a-4f70-a270-f75760994184
消费:9fae26f3-0b6e-4fbd-9620-040667efe0af
消费:95bb1d88-e08a-4f70-a270-f75760994184
队列为空,等待生产
生产:13d304bc-dff3-44a4-9527-2e0facd884e7
生产:2693e069-bae1-4beb-adcd-a3c3bf5d232b
消费:13d304bc-dff3-44a4-9527-2e0facd884e7
消费:2693e069-bae1-4beb-adcd-a3c3bf5d232b

以上就是怎么在Java多线程中使用wait、notify、notifyAll,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注行业资讯频道。

0