多线程学习总结(十一)——线程安全之线程间的通信notify和notifyAll

    声明:文章内容全都是自己的学习总结,如有不对的地方请大家帮忙指出。有需要沟通交流的可加我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的效率会更高。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值