精华内容
下载资源
问答
  • 主要介绍了 java notify和notifyAll的对比的相关资料,需要的朋友可以参考下
  • notify和notifyAll

    2020-03-18 15:04:51
    1 对象的方法,用于唤醒等待池中的线程,将等待池中的线程唤醒至锁池; 2 notify()随机唤醒等待池中的任意一个线程至锁池,notifyAll()唤醒等待池中所有的线程至锁池 ...
    1 对象的方法,用于唤醒等待池中的线程,将等待池中的线程唤醒至锁池;
    2 notify()随机唤醒等待池中的任意一个线程至锁池,notifyAll()唤醒等待池中所有的线程至锁池
    展开全文
  • notifyAll()

    2020-01-31 06:21:20
    当做多线程同步时,等待-通知机制是我们比较常用的一种选择,而 Java 中,等待-通知机制有多种实现,我们接触最早也是最熟悉的,应该就是 Java 语言内置的 synchronized 配合 wait()/notify()/notifyAll() 这三个...

    一. 序

    当做多线程同步时,等待-通知机制是我们比较常用的一种选择,而 Java 中,等待-通知机制有多种实现,我们接触最早也是最熟悉的,应该就是 Java 语言内置的 synchronized 配合 wait()/notify()/notifyAll() 这三个方法来实现。

    如何利用 synchronized 实现等待-通知机制,我想大家都比较熟悉,就无需多说了。notify() 和 notifyAll() 都可以唤醒等待的线程,但是应该使用 notify() 还是 notifyAll() 就比较有争议了。

    一种比较流行的说法就是,除非深思熟虑,否者尽量使用 notifyAll()

    我们今天就这个问题,来讨论一下这两个方法如何选择。

    二. 等待-通知机制

    2.1 什么是等待通知机制

    在此之前,先来聊聊什么是等待-通知机制,以及它能解决什么问题?

    在使用并发编程时,利用多线程来提高任务的执行效率,但是每个线程在执行时,都有一些先决条件需要被满足。例如生产者消费者模式下,消费者线程能够执行的先决条件,就是生产者产生了一个待消费的数据。

    那么如果线程要求的条件,不满足时,循环等待是一种方案,循环间隔一段时间,再重新尝试验证条件是否满足,但是这样的循环等待,在某些场景下,可能会循环很多次,导致大量消耗 CPU 资源。

    更好的方案,则是等待-通知机制。当线程要求的条件不满足时,主动进入等待状态,等线程等待的条件,再次被满足后,通知这个等待的线程重新执行。这就解决了 CPU 资源,因为循环而导致消耗的问题。

    对标到 Synchronized 中,被 Synchronized 标记的代码块,被称为临界区,在同一时刻,只有一个线程能过获取 Synchronized 的互斥锁,进入临界区执行。

    线程处于临界区时,一旦发现执行条件不满足时,则可以调用 wait() 或者 wait(long) 方法,进入等待,例如消费者发现没有更多需要处理的数据,此时就调用 wait() 方法,进入等待,等待生产者产生一条新的待消费数据。接下来如果生产者线程,产生了一个新的数据后,就需要唤醒之前等待的消费者线程,去处理这条数据。这里的唤醒操作,就是调用 notify() 或者 notifyAll() 方法。

    可以看到,notify() 和 notifyAll() 的作用有些类似,都是去唤醒等待中的线程。但是也正如他们方法名所描述的,notify() 会"随机"唤醒一个等待线程,而 notifyAll() 会尝试唤醒所有的等待中的线程。

    注意这里的唤醒,并不是真的唤醒去执行,实际上只是让处于等待的线程,有重新获取锁的争抢权,也就是说,哪怕此时有一百个线程处于等待状态,此时调用 notifyAll() 也只会有一个线程获取到锁,允许进入临界区执行。

    这在底层中,其实是利用了两个等待队列来实现的,分别是入口队列(EntrySet)和等待队列(WaitSet)。

    为什么说“除非深思熟虑,尽量使用 notifyAll()”?

     

    被 Synchronized 阻塞等待的线程,会进入入口队列,而当条件不满足时,主动调用 wait() 方法进入等待的线程,则会进入等待队列。在等待队列中的线程,如果不被唤醒,则永远没有锁的争抢权,无法获取锁也就无法被执行。

    2.2 为什么说尽量使用 notifyAll?

    终于进入主题了,就前面的描述,看似应该是使用 notify() 更好一些,因为即便我们通知了等待队列中,所有的线程,但同一时刻,也只有一个线程可以获取互斥锁,进入临界区执行,这么看来 notify() 会更高效一些。

    但是这里埋下来一个风险,就是只使用 notify() 可能会导致某些线程,一直处于等待队列中,而永远不会被唤醒并获得执行权

    理想情况下,一次等待(wait)对应一次通知(notify),是非常完美的,但是实际场景下,可能做不到。

    例如,在多生产者消费者模式下,待处理的数据队列只有一条数据了,理想场景下,消费者在处理掉一条数据后,理论上应该唤醒生产着再生产一条新的待消费数据。可是 notify() 是随机唤醒,也就是它可能会唤醒一个消费者线程,这个消费者线程,发现没有待处理的数据,此时条件不满足,又主动进入等待队列。

    也正是因为如此,在并发编程中有个范式模板:

     synchronized(this){
     while(条件不满足) {
     wait();
     }
     // ...
     }
    

    这段代码,大家应该很熟悉,notify() 只能保证唤醒一个线程,但是不保证线程执行的时候,曾经的等待条件已经被满足了。为了保证可靠性,此处使用循环检测的方式,只有必要条件满足时,才继续执行。

    正是因为 notify() 随机唤醒的特点,导致在多条件的情况下,会导致某些线程永远不会被通知到。稳妥的方式,是使用 notifyAll(),让等待中的线程,都有一次再执行的权利。

    这也就是为什么说,除非深思熟虑,否则尽量使用 notifyAll()

    2.3 什么是深思熟虑?

    使用 notifyAll() 主要是为了稳定,减少程序的复杂度,我们程序员,解决的是一系列工程问题,虽然有时候需要挑战一些性能的极限,但是大多数时候应该是以稳定且易读易维护为出发点实现功能。

    但是在程序的世界中,永远没有绝对的银丹。不带场景去分析问题,都是耍流氓。

    前面说 “除非深思熟虑”,那什么场景下才可以用到深思熟虑?

    其实只要满足三个条件即可:

    1. 线程进入等待队列的条件相同。
    2. 在满足条件时,所有线程执行的逻辑相同。
    3. 当一个线程执行完(无论是否异常),必定唤醒一个线程。

    怎么理解呢?

    当所有的线程,执行相同的业务逻辑,那么触发等待的条件也必定相同,只要保证一个线程在退出的时候,必定调用 notify() 唤醒一个线程,那么它就可以只使用 notify() 方法。

    生产者消费者模式,必定是不满足的,它把线程分为两个性质。但是这种情况下,使用 Lock&Condition 会更适合。

    三. 小结时刻

    到这里,我相信大家已经理解了 notify 和 notifyAll 的区别了,在多数场景下,推荐使用 notifyAll() 的主要原因就是因为稳定,毕竟我们大多数时候解决的是工程问题。

    展开全文
  • notify notifyAll 死锁

    2018-02-23 15:11:00
    从一个死锁分析wait,notify,notifyAll 泡芙掠夺者关注 2017.08.24 22:00*字数 1361阅读 249评论 3喜欢 7赞赏 1 本文通过wait(),notify(),notifyAll()模拟生产者-消费者例子,说明为什么使用notify()...

    从一个死锁分析wait,notify,notifyAll

    96 
    泡芙掠夺者 
    2017.08.24 22:00* 字数 1361 阅读 249评论 3

    本文通过wait(),notify(),notifyAll()模拟生产者-消费者例子,说明为什么使用notify()会发生死锁。

    1. 代码示例

    1.1 生产者

    public class Producer implements Runnable {
        List<Integer> cache;
    
        public Producer(List<Integer> cache) {
            this.cache = cache;
        }
    
        @Override
        public void run() {
            while (true) {
                produce();
            }
        }
    
        private void produce() {
            synchronized (cache) {
                try {
                    while (cache.size() == 1) {
                        cache.wait();
                    }
    
                    // 模拟一秒生产一条消息
                    Thread.sleep(1000);
                    cache.add(new Random().nextInt());
    
                    cache.notify();
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    1.2 消费者

    public class Consumer implements Runnable {
        List<Integer> cache;
    
        public Consumer(List<Integer> cache) {
            this.cache = cache;
        }
    
        @Override
        public void run() {
            while (true) {
                consume();
            }
        }
    
        private void consume() {
            synchronized (cache) {
                try {
                    while (cache.isEmpty()) {
                        cache.wait();
                    }
    
                    System.out.println("Consumer consumed [" + cache.remove(0) + "]");
                    cache.notify();
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    1.3 测试代码

    1.3.1 一个生产者一个消费者
    public class WaitNotifyTest {
        public static void main(String[] args) throws Exception {
            List<Integer> cache = Lists.newArrayList();
            new Thread(new Consumer(cache)).start();
            new Thread(new Producer(cache)).start();
        }
    }
    
    运行结果:
    Consumer consumed [169154454]
    Consumer consumed [511734]
    Consumer consumed [-25784306]
    Consumer consumed [1648046130]
    ……
    

    从控制台可以看出,每隔一秒钟消费一条数据,完美的生产者消费者模型!

    1.3.2 多个消费者多个生产者

    既然生产者和消费者都继承了Runnable,就应该多线程运行嘛~~

    public class WaitNotifyTest {
        public static void main(String[] args) throws Exception {
            List<Integer> cache = Lists.newArrayList();
            new Thread(new Consumer(cache)).start();
            new Thread(new Consumer(cache)).start();
            new Thread(new Consumer(cache)).start();
    
            new Thread(new Producer(cache)).start();
            new Thread(new Producer(cache)).start();
            new Thread(new Producer(cache)).start();
        }
    }
    
    运行结果:
    Consumer consumed [-1658797021]
    Consumer consumed [-2050633449]
    

    程序运行一会后,控制台就没输出了!!! 通过jstack命令可以分析出当前demo发生了死锁。

    1.3.3 将Consumer和Producer中的notify()换成notifyAll()试试
    运行结果
    Consumer consumed [-781807640]
    Consumer consumed [42787175]
    Consumer consumed [327937050]
    Consumer consumed [2140968760]
    ……
    

    程序又可以欢乐的跑起来了!

    我到底做错什么了!

    2. notify和notifyAll的区别

    1.3.2和1.3.3的不同之处只是将notify换成了notifyAll,肯定是这里有问题

    在说明notify和notifyAll的区别之前,先阐述两个概念:锁池和等待池
    • 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
    • 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中。
    再说notify和notifyAll的区别
    • 线程调用了对象的 wait()方法,便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁(即不会参与线程调度,大家理解就好~~)。
    • notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。
    • notify调用后,只会将等待池中的一个随机线程移到锁池。
    知道上面的原理后,1.3.x的例子就很好理解了

    1.3.1:因为只有一个生产者和消费者,所以等待池中始终只有一个线程,要么就是生产者唤醒消费者,要么消费者唤醒生产者,所以程序可以成功跑起来;
    1.3.2:为了简化分析过程,假设两个消费者线程C1、C2,一个生产者线程P1。
    时序过程如下(只是一种可能性,不绝对,毕竟只是个例子嘛~~):

    • C1,C2观察到缓存cache中无数据,进入等待池;
    • P1获取锁并设置cache数据,通过notify唤醒等待池中某个线程C1,假设C1被唤醒并放入锁池,然后P1释放锁、继续循环重新获取锁并因为检测到cache.size()==1而进入等待池;
    • 此时锁池中的线程为C1,C1会竞争到锁,从而消费数据,然后执行notify方法并释放锁,并假设其notify方法会将C2从等待池移入锁池;
    • C2检测到cache为空,执行await()使自身进入锁池。因为自身的阻塞所以不能唤醒C1或P1,从而导致死锁!

    1.3.3:分两种情况分析:

    • cache为空
    • 先假设所有线程P都一直停留在等待池。但是这种情况是不可能存在的,因为cache肯定是某个C线程消费后才为空,然后该C线程会执行notifyAll方法将所有等待池中的线程都移入锁池,所以不可能所有P线程一直在等待池;
    • 既然P线程不可能一直在等待池,那么这种P线程会竞争锁从而设置cache的值导致cache不为空;
    • cache不空的情况分析过程同上

    3 使用wait、notify的基本套路

    下面是effective java中推荐的标准写法:

    synchronized (obj) {
        while (<condition does not hold>)
            obj.wait(timeout);
        // Perform action appropriate to condition
    }
    

    为什么要用while,改成if行不行,就像下面这样:

    synchronized (obj) {
        if (<condition does not hold>)
            obj.wait(timeout);
        // Perform action appropriate to condition
    }
    

    我们将1.1和1.2代码中的while换成if,启动一个生产者,两个消费者线程,某个消费者线程会出现数组下标越界的异常,代码及原因分析如下:

    public class WaitNotifyTest {
        public static void main(String[] args) throws Exception {
            List<Integer> cache = Lists.newArrayList();
            new Thread(new Consumer(cache)).start();
            new Thread(new Consumer(cache)).start();
            Thread.sleep(1000);
            new Thread(new Producer(cache)).start();
        }
    }
    
    • 消费者C1、C2发现cache为空,相继进入等待池;
    • P1生产数据,放入cache并唤醒C1,同时自己进入等待池;
    • C1消费数据,唤醒C2,C2从cache.wait()处开始执行,因为我们将while(cache.isEmpty())改成了if(cache.isEmpty()),C2不会再次检查cache是否为空,而是直接执行后续代码,这时cache的数据已经被C1消费完了,调用cache.get(0)产生数组下标越界!

    转载于:https://www.cnblogs.com/diegodu/p/8462148.html

    展开全文
  • Arnej/avoid big notifyall

    2021-01-09 01:21:35
    <div><p>this is a smaller change to avoid costly notifyAll. Please have a look but do not merge today. please review.</p><p>该提问来源于开源项目:vespa-engine/vespa</p></div>
  • 本文主要介绍Java notify和notifyAll的知识,这里整理详细的资料来说明notify 和NotifAll的区别,有需要的小伙伴可以参考下
  • notify和notifyAll区别

    2019-04-04 10:53:32
    很多人在回答第二个问题的时候会想当然的说notify()是唤醒一个线程,notifyAll()是唤醒全部线程,但是唤醒然后呢,不管是notify()还是notifyAll(),最终拿到锁的只会有一个线程,那它们到底有什么区别呢?...

    1:wait()方法外面为什么是while循环而不是if判断

    2:  结尾处的为什么要用notifyAll()方法,用notify()行吗。

    很多人在回答第二个问题的时候会想当然的说notify()是唤醒一个线程,notifyAll()是唤醒全部线程,但是唤醒然后呢,不管是notify()还是notifyAll(),最终拿到锁的只会有一个线程,那它们到底有什么区别呢?

    其实这是一个对象内部锁的调度问题,要回答这两个问题,首先我们要明白java中对象锁的模型,JVM会为一个使用内部锁(synchronized)的对象维护两个集合,Entry SetWait Set,也有人翻译为锁池和等待池,意思基本一致。

    对于Entry Set:如果线程A已经持有了对象锁,此时如果有其他线程也想获得该对象锁的话,它只能进入Entry Set,并且处于线程的BLOCKED状态。

    对于Wait Set:如果线程A调用了wait()方法,那么线程A会释放该对象的锁,进入到Wait Set,并且处于线程的WAITING状态。

    还有需要注意的是,某个线程B想要获得对象锁,一般情况下有两个先决条件,一是对象锁已经被释放了(如曾经持有锁的前任线程A执行完了synchronized代码块或者调用了wait()方法等等),二是线程B已处于RUNNABLE状态。

    那么这两类集合中的线程都是在什么条件下可以转变为RUNNABLE呢?

    对于Entry Set中的线程,当对象锁被释放的时候,JVM会唤醒处于Entry Set中的某一个线程,这个线程的状态就从BLOCKED转变为RUNNABLE。

    对于Wait Set中的线程,当对象的notify()方法被调用时,JVM会唤醒处于Wait Set中的某一个线程,这个线程的状态就从WAITING转变为RUNNABLE;或者当notifyAll()方法被调用时,Wait Set中的全部线程会转变为RUNNABLE状态。所有Wait Set中被唤醒的线程会被转移到Entry Set中。

    然后,每当对象的锁被释放后,那些所有处于RUNNABLE状态的线程会共同去竞争获取对象的锁,最终会有一个线程(具体哪一个取决于JVM实现,队列里的第一个?随机的一个?)真正获取到对象的锁,而其他竞争失败的线程继续在Entry Set中等待下一次机会。

    有了这些知识点作为基础,上述的两个问题就能解释的清了。

    首先来看第一个问题,我们在调用wait()方法的时候,心里想的肯定是因为当前方法不满足我们指定的条件,因此执行这个方法的线程需要等待直到其他线程改变了这个条件并且做出了通知。那么为什么要把wait()方法放在循环而不是if判断里呢,其实答案显而易见,因为wait()的线程永远不能确定其他线程会在什么状态下notify(),所以必须在被唤醒、抢占到锁并且从wait()方法退出的时候再次进行指定条件的判断,以决定是满足条件往下执行呢还是不满足条件再次wait()呢。

    就像在本例中,如果只有一个生产者线程,一个消费者线程,那其实是可以用if代替while的,因为线程调度的行为是开发者可以预测的,生产者线程只有可能被消费者线程唤醒,反之亦然,因此被唤醒时条件始终满足,程序不会出错。但是这种情况只是多线程情况下极为简单的一种,更普遍的是多个线程生产,多个线程消费,那么就极有可能出现唤醒生产者的是另一个生产者或者唤醒消费者的是另一个消费者,这样的情况下用if就必然会现类似过度生产或者过度消费的情况了,典型如IndexOutOfBoundsException的异常。所以所有的java书籍都会建议开发者永远都要把wait()放到循环语句里面

    然后来看第二个问题,既然notify()和notifyAll()最终的结果都是只有一个线程能拿到锁,那唤醒一个和唤醒多个有什么区别呢?

    耐心看下面这个两个生产者两个消费者的场景,如果我们代码中使用了notify()而非notifyAll(),假设消费者线程1拿到了锁,判断buffer为空,那么wait(),释放锁;然后消费者2拿到了锁,同样buffer为空,wait(),也就是说此时Wait Set中有两个线程;然后生产者1拿到锁,生产,buffer满,notify()了,那么可能消费者1被唤醒了,但是此时还有另一个线程生产者2在Entry Set中盼望着锁,并且最终抢占到了锁,但因为此时buffer是满的,因此它要wait();然后消费者1拿到了锁,消费,notify();这时就有问题了,此时生产者2和消费者2都在Wait Set中,buffer为空,如果唤醒生产者2,没毛病;但如果唤醒了消费者2,因为buffer为空,它会再次wait(),这就尴尬了,万一生产者1已经退出不再生产了,没有其他线程在竞争锁了,只有生产者2和消费者2在Wait Set中互相等待,那传说中的死锁就发生了。

    但如果你把上述例子中的notify()换成notifyAll(),这样的情况就不会再出现了,因为每次notifyAll()都会使其他等待的线程从Wait Set进入Entry Set,从而有机会获得锁。

    其实说了这么多,一句话解释就是之所以我们应该尽量使用notifyAll()的原因就是,notify()非常容易导致死锁。当然notifyAll并不一定都是优点,毕竟一次性将Wait Set中的线程都唤醒是一笔不菲的开销,如果你能handle你的线程调度,那么使用notify()也是有好处的。



     

    展开全文
  • notfiy notifyall区别

    2018-03-18 12:04:50
    异步 异步就是两者是独立的 比如页面发起ajax请求 我们也能操作浏览页面其他的东西同步 同步指的是两者有顺序 要进行...notifyall() notify()的问题 这是一个多线程生产者 消费者的问题 使用 wait notify进行模...
  • java notify notifyAll

    2015-11-05 14:21:00
    notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。 void notify(): 唤醒一个正在等待该对象的线程。void notifyAll(): 唤醒所有正在等待该对象的线程。 两者的最大区别在于: ...
  • notify() 和 notifyAll() 有什么区别?

    万次阅读 多人点赞 2019-05-15 16:22:16
    notify() 和 notifyAll() 有什么区别? 先解释两个概念。 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。 锁池...
  • java notify notifyall 区别

    2017-04-20 11:47:04
    java notify notifyall 区别
  • wait notify notifyAll

    2016-06-28 22:16:00
    等待通知机制是指一个线程A调用了某个对象的wait的方法后进行等待状态,而另一个线程B调用了该对象的notify或者notifyAll方法,线程A收到通知后从对象的wait方法返回,进而执行后续操作,上述两个线程通过对象来完成...
  • <p>Notifyall - 1.0 : Disable <p><code>Send admin email message to all account holders. <b>-><a href=/notifyall TARGET = "_blank">send now!</a><-</b>...
  • 文章目录前言目标关键字一、notify 定义线程可以通过以下三种方式之一成为对象监视器的所有者:二、notifyAll定义总结参考 前言 莫信直中直,须防仁不仁。 目标 Object notify notifyAll基本概念 关键字 notify ...
  • 主要介绍了Java wait和notifyAll实现简单的阻塞队列,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 本篇文章对Java中多线程notify与notifyall的区别进行了详细的分析介绍。需要的朋友参考下
  • 主要介绍了Java线程中的notifyAll唤醒操作,非常不错,具有参考借鉴价值,需要的朋友可以参考下
  • Java notify and notifyAll

    2017-09-14 19:13:23
    JAVA 多线程 notify 和 notifyAll 的区别
  • wait与notify与notifyAll

    2020-01-11 19:59:51
    1 wait notify notifyAll方法的调用时需要对对象加锁 2 调用wait方法会,将当前线程放置于对象的等待队列,线程状态从running变为waiting. 3 notify或notifyAll调用后,等待线程并不会马上从wait返回,需要等notify和...
  • Java中 notify和notifyAll*

    2020-09-10 08:54:26
    线程调用notify()或者notifyAll()后,线程不会释放它自己的锁,直到该synchronized包裹的方法执行完以后,它会释放锁,所以notify()和notifyAll()一般会放在同步代码块儿的最后一行 notify()和notifyAll()需要包裹...
  • notify和notifyall的区别

    2020-12-27 20:55:48
    调用wait的线程的唤醒,一般通过notify和notifyAll,但是两者之间有什么区别呢? 分析 线程调用synchronized方法或者synchronized代码块需要获取对象锁,如果没有获取则进入锁池 线程调用wait方法进入等待池,此时...
  • Object的notify和notifyAll方法的区别

    千次阅读 多人点赞 2019-01-28 14:29:41
    既然notify会唤醒一个线程,并获取锁,notifyAll会唤醒所有线程并根据算法选取其中一个线程获取锁,那最终结果不都是只有一个线程获取锁吗?那JDK为什么还需要做出来这两个方法呢?这两种同步方法本质上会有什么区别...
  • 6、notify 和 notifyAll的区别 notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。...
  • 很多朋友对java中的notyfy()和notifyAll()的本质区别不了解,今天小编抽空给大家整理一篇教程关于Java中的notyfy()和notifyAll()的本质区别,需要的朋友参考下吧
  • wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视。本文对这些关键字的使用进行了描述。  在 Java 中可以用 wait、notify 和 notifyAll 来实现...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,618
精华内容 3,047
关键字:

notifyall