今天正好碰到这个问题,也疑惑了很久。看了一圈知乎上的答案,感受没说到根上。因此本身又好好Google了一下,终于找到了让本身信服的解释。html
先说两个概念:锁池和等待池java
Reference: java中的锁池和等待池
而后再来讲notify和notifyAll的区别程序员
Reference: 线程间协做:wait、notify、notifyAll
综上,所谓唤醒线程,另外一种解释能够说是将线程由等待池移动到锁池,notifyAll调用后,会将所有线程由等待池移到锁池,而后参与锁的竞争,竞争成功则继续执行,若是不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。面试
有了这些理论基础,后面的notify可能会致使死锁,而notifyAll则不会的例子也就好解释了安全
仍是直接上代码:多线程
public class WaitAndNotify {
并发
public static void main(String[] args) {
dom
Object co = new Object();
ide
System.out.println(co);
函数
for (int i = 0; i < 5; i++) {
MyThread t = new MyThread("Thread" + i, co);
t.start();
}
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("-----Main Thread notify-----");
synchronized (co) {
co.notify();
}
TimeUnit.SECONDS.sleep(2);
System.out.println("Main Thread is end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class MyThread extends Thread {
private String name;
private Object co;
public MyThread(String name, Object o) {
this.name = name;
this.co = o;
}
public void run() {
System.out.println(name + " is waiting.");
try {
synchronized (co) {
co.wait();
}
System.out.println(name + " has been notified.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
java.lang.Object@1540e19d
Thread1 is waiting.
Thread2 is waiting.
Thread0 is waiting.
Thread3 is waiting.
Thread4 is waiting.
-----Main Thread notify-----
Thread1 has been notified.
Main Thread is end.
将其中的那个notify换成notifyAll,运行结果:
Thread0 is waiting.
Thread1 is waiting.
Thread2 is waiting.
Thread3 is waiting.
Thread4 is waiting.
-----Main Thread notifyAll-----
Thread4 has been notified.
Thread2 has been notified.
Thread1 has been notified.
Thread3 has been notified.
Thread0 has been notified.
Main Thread is end.
运行环境jdk8,结论:
notify唤醒一个等待的线程;notifyAll唤醒全部等待的线程。
wait, notify 和 notifyAll,这些在多线程中被常常用到的保留关键字,在实际开发的时候不少时候却并无被你们重视。本文对这些关键字的使用进行了描述。
在 Java 中能够用 wait、notify 和 notifyAll 来实现线程间的通讯。。举个例子,若是你的Java程序中有两个线程——即生产者和消费者,那么生产者能够通知消费者,让消费者开始消耗数据,由于队列缓冲区中有内容待消费(不为空)。相应的,消费者能够通知生产者能够开始生成更多的数据,由于当它消耗掉某些数据后缓冲区再也不为满。
咱们能够利用wait()来让一个线程在某些条件下暂停运行。例如,在生产者消费者模型中,生产者线程在缓冲区为满的时候,消费者在缓冲区为空的时候,都应该暂停运行。若是某些线程在等待某些条件触发,那当那些条件为真时,你能够用 notify 和 notifyAll 来通知那些等待中的线程从新开始运行。不一样之处在于,notify 仅仅通知一个线程,而且咱们不知道哪一个线程会收到通知,然而 notifyAll 会通知全部等待中的线程。换言之,若是只有一个线程在等待一个信号灯,notify和notifyAll都会通知到这个线程。但若是多个线程在等待这个信号灯,那么notify只会通知到其中一个,而其它线程并不会收到任何通知,而notifyAll会唤醒全部等待中的线程。
在这篇文章中你将会学到如何使用 wait、notify 和 notifyAll 来实现线程间的通讯,从而解决生产者消费者问题。若是你想要更深刻地学习Java中的多线程同步问题,我强烈推荐阅读Brian Goetz所著的《Java Concurrency in Practice | Java 并发实践》,不读这本书你的 Java 多线程征程就不完整哦!这是我最向Java开发者推荐的书之一。
尽管关于wait和notify的概念很基础,它们也都是Object类的函数,但用它们来写代码却并不简单。若是你在面试中让应聘者来手写代码,用wait和notify解决生产者消费者问题,我几乎能够确定他们中的大多数都会无所适从或者犯下一些错误,例如在错误的地方使用 synchronized 关键词,没有对正确的对象使用wait,或者没有遵循规范的代码方法。说实话,这个问题对于不常使用它们的程序员来讲确实使人感受比较头疼。
第一个问题就是,咱们怎么在代码里使用wait()呢?由于wait()并非Thread类下的函数,咱们并不能使用Thread.call()。事实上不少Java程序员都喜欢这么写,由于它们习惯了使用Thread.sleep(),因此他们会试图使用wait() 来达成相同的目的,但很快他们就会发现这并不能顺利解决问题。正确的方法是对在多线程间共享的那个Object来使用wait。在生产者消费者问题中,这个共享的Object就是那个缓冲区队列。
第二个问题是,既然咱们应该在synchronized的函数或是对象里调用wait,那哪一个对象应该被synchronized呢?答案是,那个你但愿上锁的对象就应该被synchronized,即那个在多个线程间被共享的对象。在生产者消费者问题中,应该被synchronized的就是那个缓冲区队列。(我以为这里是英文原文有问题……原本那个句末就不该该是问号否则不太通……)
如今你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个必定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。由于线程是在某些条件下等待的——在咱们的例子里,即“若是缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,致使即便条件没被知足,你的线程你也有可能被错误地唤醒。因此若是你不在线程被唤醒后再次使用while循环检查唤醒条件是否被知足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。因此记住,永远在while循环而不是if语句中使用wait!我会推荐阅读《Effective Java》,这是关于如何正确使用wait和notify的最好的参考资料。
基于以上认知,下面这个是使用wait和notify函数的规范代码模板:
1 2 3 4 5 6 7 8 |
|
就像我以前说的同样,在while循环里使用wait的目的,是在线程被唤醒的先后都持续检查条件是否被知足。若是条件并未改变,wait被调用以前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会致使死锁问题。
下面咱们提供一个使用wait和notify的范例程序。在这个程序里,咱们使用了上文所述的一些代码规范。咱们有两个线程,分别名为PRODUCER(生产者)和CONSUMER(消费者),他们分别继承了了Producer和Consumer类,而Producer和Consumer都继承了Thread类。Producer和Consumer想要实现的代码逻辑都在run()函数内。Main线程开始了生产者和消费者线程,并声明了一个LinkedList做为缓冲区队列(在Java中,LinkedList实现了队列的接口)。生产者在无限循环中持续往LinkedList里插入随机整数直到LinkedList满。咱们在while(queue.size == maxSize)循环语句中检查这个条件。请注意到咱们在作这个检查条件以前已经在队列对象上使用了synchronized关键词,于是其它线程不能在咱们检查条件时改变这个队列。若是队列满了,那么PRODUCER线程会在CONSUMER线程消耗掉队列里的任意一个整数,并用notify来通知PRODUCER线程以前持续等待。在咱们的例子中,wait和notify都是使用在同一个共享对象上的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
|
为了更好地理解这个程序,我建议你在debug模式里跑这个程序。一旦你在debug模式下启动程序,它会中止在PRODUCER或者CONSUMER线程上,取决于哪一个线程占据了CPU。由于两个线程都有wait()的条件,它们必定会中止,而后你就能够跑这个程序而后看发生什么了(颇有可能它就会输出咱们以上展现的内容)。你也可使用Eclipse里的Step into和Step over按钮来更好地理解多线程间发生的事情。
1. 你可使用wait和notify函数来实现线程间通讯。你能够用它们来实现多线程(>3)之间的通讯。
2. 永远在synchronized的函数或对象里使用wait、notify和notifyAll,否则Java虚拟机会生成 IllegalMonitorStateException。
3. 永远在while循环里而不是if语句下使用wait。这样,循环会在线程睡眠先后都检查wait的条件,并在条件实际上并未改变的状况下处理唤醒通知。
4. 永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用wait。
5. 基于前文说起的理由,更倾向用 notifyAll(),而不是 notify()。
这是关于Java里如何使用wait, notify和notifyAll的全部重点啦。你应该只在你知道本身要作什么的状况下使用这些函数,否则Java里还有不少其它的用来解决同步问题的方案。例如,若是你想使用生产者消费者模型的话,你也可使用BlockingQueue,它会帮你处理全部的线程安全问题和流程控制。若是你想要某一个线程等待另外一个线程作出反馈再继续运行,你也可使用CycliBarrier或者CountDownLatch。若是你只是想保护某一个资源的话,你也可使用Semaphore。