声明:文章内容全都是自己的学习总结,如有不对的地方请大家帮忙指出。有需要沟通交流的可加我QQ群:425120333
关于notify和notifyAll这两个方法的区别从方法的命名上就能看出来,一个是唤醒在对象上等待的一个阻塞线程,一个是唤醒在对象上等待的所有线程。
再多数情况下我们习惯性的使用notifyAll方法,因为能保证不出错,而使用了notify方法可能在没有考虑清楚的情况下就让所有线程都死掉了,
从而使得进程虽然在运行,其实已经没动作了。
那既然notifyAll能保证不出错了,为啥还要有notify,意义在哪?其实使用notify是出于性能或者是效率的考虑。举一个简单的例子:
买票问题,因为只有一个窗口,那么在同一时刻最多只有一个人能买到票,这个人买完后要通知其他人我买好了,使用notify相当于只是告诉一个人我买好了,
只有一个人知道,其他人还是等着的,然后这个人去买票,买好后通知下一个,直到所有人都买好。而使用notifyAll相当于一个人买好票后,拿出喇叭喊了一声
我买好了,然后走了,其他等待的都知道这个人买好了,都想轮到自己买,
大家就都去抢这个机会,当然每次都是只有一个人能抢到,然后买完,再喊一声,直到大家都买完。这么一讲,大家应该能明白其中的区别了。
虽然使用notify相对来说效率会高一些,但是容易出错,看下面一个例子:
public class TestNotify {
public static void main(String[] args) {
PlateTest plateTest = new PlateTest();
//用多个线程
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new Producer(plateTest));
t.start();
}
//多个线程启动
for (int i = 0; i < 10; i++) {
Thread t1 = new Thread(new Customer(plateTest));
t1.start();
}
}
}
class PlateTest {
private List<String> list = new ArrayList<String>();
public synchronized void put() {
while (list.size() > 0) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
list.add("苹果");
System.out.println("往容器中放入一个成功,现在的大小为: " + list.size() + ",内容为:" + list.get(0));
this.notify();
}
public synchronized void get() {
while (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
String str = list.get(0);
list.remove(str);
System.out.println("从容器中取出一个成功,现在的大小为: " + list.size() + ",取出内容为:" + str);
this.notify();
}
}
class Producer implements Runnable {
PlateTest plateTest;
public Producer(PlateTest plateTest) {
this.plateTest = plateTest;
}
@Override
public void run() {
for (;;) {
plateTest.put();
}
}
}
class Customer implements Runnable {
PlateTest plateTest;
public Customer(PlateTest plateTest) {
this.plateTest = plateTest;
}
@Override
public void run() {
for (;;) {
plateTest.get();
}
}
}
这段代码和前面多线程学习总结(九)中的代码基本类似没什么区别,唯一的不同就是在main方法中使用了多个线程启动,而不是单个,而问题也是
很明显,启动的线程只要是大于一,就有可能使得代码运行不下去(不是越多就越快运行不下去),如果改为notifyAll就不会出现这种情况。
使用notify运行不下去的模拟分析(notify丢失的解释):
上面问题该怎么解释呢,我给大家一步步分析,因为加了synchronized关键字,所有同步块的内容在一个时间点只能有一个线程执行,这是前提条件。
现在假定启动了2个producer线程,分别为p1和p2,2个customer线程,分别为c1和c2,然后2个customer先执行,因为发现list.size() == 0
所以这c1和c2都wait(都是在plateTest对象上wait)然后p1执行将list.size()改为1,然后唤醒一个在plateTest的线程,暂定为c1。
这时wait的线程只有c2,然后运行的是其他三个, list.size() =1;接着p1执行,发现list.size() ==1,p1就wait了,同理p2也wait了,然后c1运行,
能正常运行,唤醒一个(这时wait的总共有三个c2,p1,p2),刚好是c2;这时运行着的是c1和c2,wait的是p1和p2,list.size() =0;然后c1和c2相继运行,
发现list.size() ==0就都wait了,这时四个线程都wait了,没有一个在运行,所以导致程序就运行不下去了。
有人可能会说刚好出现这种情况也太巧了,但是不管怎么说有这个可能就能让你的程序运行不下去,说明代码有问题。通过上面的分析,也明白了为啥用
notifyAll不会有问题了,毕竟每次都是唤醒所有在wait上的线程。大家可以结合http://www.cnblogs.com/lnlvinso/p/4753554.html这个链接上的内容
加深一下理解。
关于上面的代码除了用notifyAll实现之外还有另一种办法就是在使用wait()方法之前调用notify(),代码示例如下:
try {
System.out.println(Thread.currentThread().getName() + "will wait");
this.notify();
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
但因为notify选择唤醒其中一个线程。选择是任意性的,由具体实现做出决定.因此设计JAVA程序时,不要依赖于这一点.上面的代码我执行后发现虽然保证了至少
有一个是一致运行的,但总会出现p1唤醒p2,p2又唤醒p1,一直在循环,好久才唤醒c1,可是c1又唤醒c2,然后c2又唤醒c1的情况,所以这种方法还不如用notifyAll
在这里提出,只是给大家一个解决的思路,真正开发时不要这样处理。notify的真正使用场景还是唤醒哪一个都可以没有影响的情况下,这时notifyAll
虽然也可以,但是notify的效率会更高。