资讯专栏INFORMATION COLUMN

Java中wait、notify、notifyAll使用详解

rozbo / 2542人阅读

摘要:用法中规定,在调用者三个方法时,当前线程必须获得对象锁。作用方法作用线程自动释放占有的对象锁,并等待。当生产者生产了一个数据或者消费者消费了一个数据之后,使用方法来通知所有等待当前对象锁的线程,但是一次只会有一个等待的线程能拿到锁。

基础知识

首先我们需要知道,这几个都是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当前对象的线程,但是被唤醒的线程会再次去竞争对象锁。因为一次只有一个线程能拿到锁,所有其他没有拿到锁的线程会被阻塞。推荐使用。
实际案例

接下来我们就使用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

由结果也可以看出来,生产和消费是无规则的,因为虽然notifyAll()通知了所有的等待线程,但是不确定那个线程中能拿到对象锁。但是也有一个很明显的问题,因为同时只有一个线程能拿到对象锁,生产者和消费者不可能同时运行。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74455.html

相关文章

  • Java 基础 | Object 源码解析

    摘要:注释指出方法返回散列值。直到其他线程调用此对象的方法或方法,当前线程被唤醒进入就绪状态。总超时时间以纳秒为单位计算为。以上注释主要就是描述了,和方法的使用规范。主要的区别在于在释放同时,释放了对象锁的控制。 showImg(https://segmentfault.com/img/remote/1460000020031663?w=1240&h=734); Java 是一门面向对象的语...

    dendoink 评论0 收藏0
  • Javawait/notify/notifyAll

    摘要:等待一段时间是否有线程唤醒锁,如果没有,超时自动唤醒。随机唤醒等待队列中的等待同一个锁的一个线程,使这个线程退出等待队列,进入可运行状态。条件队列中是处于等待状态的线程,等待特定条件为真。在一般情况下,总应该调用唤醒所有需要被唤醒的线程。 方法 java.lang.Object public final native void wait() throws InterruptedExce...

    terasum 评论0 收藏0
  • Java 多线程核心技术梳理(附源码)

    摘要:本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,的使用,定时器,单例模式,以及线程状态与线程组。源码采用构建,多线程这部分源码位于模块中。通知可能等待该对象的对象锁的其他线程。 本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock的使用,定时器,单例模式,以及线程状态与线程组。 写在前面 花了一周时...

    Winer 评论0 收藏0
  • java多线程(7)wait()、notify()和notityALL()

    摘要:已经在上面有提到过,和的作用是唤醒正在的线程,是随机唤醒线程中的一个,则是唤醒全部。释放和不释放锁在多线程的操作中,锁的释放与否是必须要清楚的,是会释放锁,而则不会。 wait wait方法是Object中的方法,这个方法的功能特性:1).执行wait方法的前提是当前线程已经获取到对象的锁,也就是wait方法必须在synchronized修饰的代码块或者方法中使用。2).执行wait之...

    LiuRhoRamen 评论0 收藏0
  • java并发编程学习之线程的生命周期-wait,notify,notifyall(六)

    摘要:不释放持有的锁,释放锁。在调用方法前,必须持有锁,调用唤醒,也要持有锁。休眠一定时间后,进入就绪状态。这两个都能被方法中断当前状态。用法方获取锁判断条件,不满足继续满足执行其他业务方获取锁改变条件通知为什么是而不是会一直循环,直到条件满足。 sleep和wait sleep是Thread类的方法,wait是Object的方法。 sleep可以到处使用,wait必须是在同步方法或者代码...

    Terry_Tai 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<