前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >谈谈notify和notifyAll的异同

谈谈notify和notifyAll的异同

作者头像
JavaFish
发布2019-11-01 15:14:37
5070
发布2019-11-01 15:14:37
举报
题 图:pexels

来 源:https://www.iflym.com

预 计 阅 读 时 间:6分钟

经常在网上逛,关于在java中notify和notifyAll,经常有人有以下的说法:

notify只会通知一个在等待的对象,而notifyAll会通知所有在等待的对象,并且所有对象都会继续运行

并且,好像都有例子可以证明。上面的说法,可以说对,也可以说不对。究其原因,在于其中有一点很关键,官方的说法如下所示:

wait,notify,notifyAll: 此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者: 1、通过执行此对象的同步实例方法。 2、通过执行在此对象上进行同步的 synchronized 语句的正文。 3、对于 Class 类型的对象,可以通过执行该类的同步静态方法。 一次只能有一个线程拥有对象的监视器。

以上说法,摘自javadoc。意思即,在调用中,必须持有对象监视器(即锁),我们可以理解为需要在synchronized方法内运行。

那么由此话的隐含意思,即如果要继续由同步块包含的代码块,需要重新获取锁才可以。这句话,在javadoc中这样描述:

wait 此方法导致当前线程(称之为 T)将其自身放置在对象的等待集中,然后放弃此对象上的所有同步要求。出于线程调度目的,在发生以下四种情况之一前,线程 T 被禁用,且处于休眠状态: 1、其他某个线程调用此对象的 notify 方法,并且线程 T 碰巧被任选为被唤醒的线程。 2、其他某个线程调用此对象的 notifyAll 方法。 3、其他某个线程中断线程 T。 大约已经到达指定的实际时间。但是,如果 timeout 为零,则不考虑实际时间,在获得通知前该线程将一直等待。 然后,从对象的等待集中删除线程 T,并重新进行线程调度。然后,该线程以常规方式与其他线程竞争,以获得在该对象上同步的权利;一旦获得对该对象的控制权,该对象上的所有其同步声明都将被恢复到以前的状态,这就是调用 wait 方法时的情况。然后,线程 T 从 wait 方法的调用中返回。所以,从 wait 方法返回时,该对象和线程 T 的同步状态与调 用 wait 方法时的情况完全相同。

即必须重新进行获取锁,这样对于notifyAll来说,虽然所有的线程都被通知了。但是这些线程都会进行竞争,且只会有一个线程成功获取到锁,在这个线程没有执行完毕之前,其他的线程就必须等待了(只是这里不需要再notifyAll通知了,因为已经notifyAll了,只差获取锁了)有如下一个代码,可以重现这个现象。

首先,定义一个可以运行的线程类,如下所示:

代码语言:javascript
复制
private static final Object obj = new Object();
    static class R implements Runnable {
        int i;

        R(int i) {
            this.i = i;
        }

        public void run() {
            try {
                synchronized(obj) {
                    System.out.println("线程->  " + i + " 等待中");
                    obj.wait();
                    System.out.println("线程->  " + i + " 在运行了");
                    Thread.sleep(30000);
                }
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }

注意上面的run方法内部,我们在wait()之后,打印一句话,然后将当前代码,暂停30秒。

关于sleep方法,是这样描述的:

1、该线程不丢失任何监视器的所属权。 2、即仍然持有锁。

然后,定义一个main方法来运行这些线程,如下所示:

代码语言:javascript
复制
Thread[] rs = new Thread[10];
        for(int i = 0;i < 10;i++) {
            rs[i] = new Thread(new R(i));
        }
        for(Thread r : rs) {
            r.start();
        }

        Thread.sleep(5000);
        synchronized(obj) {
            obj.notifyAll();
        }

我们定义了10个线程,然后全部运行之。因为有wait,10个线程都会在打印出 "开始运行"之后等待。然后main方法调用notifyAll。这里的输出就会出现如下的输出:

代码语言:javascript
复制
线程-> 0 等待中
线程->  4 等待中
线程->  5 等待中
线程->  3 等待中
线程->  2 等待中
线程->  1 等待中
线程->  6 等待中
线程->  7 等待中
线程->  8 等待中
线程->  9 等待中
线程->  9 在运行了
...30秒之内,不会有其他输出

在上面的输出中,在wait之后,只有一个线程输出了"在运行了"语句,并且在一段时间内(这里为30秒),不会有其他输出。即表示,在当前代码持有锁之间,其他线程是不会输出的。

最后结论就是:被wait的线程,想要继续运行的话,它必须满足2个条件:

  1. 由其他线程notify或notifyAll了,并且当前线程被通知到了
  2. 经过和其他线程进行锁竞争,成功获取到锁了

2个条件,缺一不可。其实在实现层面,notify和notifyAll都达到相同的效果,都只会有一个线程继续运行。但notifyAll免去了,线程运行完了通知其他线程的必要,因为已经通知过了。什么时候用notify,什么时候使用notifyAll,这就得看实际的情况了。

全文完

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个优秀的废人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档