java:用CountDownLatch.await替代Object.wait实现线程阻塞/唤醒

时间:2022-06-22
本文章向大家介绍java:用CountDownLatch.await替代Object.wait实现线程阻塞/唤醒,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net/10km/article/details/53700088

线程之间经常需要一定的同步,比如线程A需要线程B的部分运算结果,但又不必等到线程B把所有的结果都算出来,否则A就要待太长时间。 下面这个例子就是这个应用场景,主线程需要等待子线程从数据库中加载记录,但是子线程把所有的记录都加载完要花挺长时间。 而实际上,主线程最开始只需要一条记录就可以继续自己的后续动作了。怎么办呢?下面的代码利用传统的Object.wait()/nofity()方法来实现:

    public void openSource() {
        // 创建一个初值为1的倒数计数器对象作为通知对象
        Object notifier=new Object();
        // 启动新线程用于长时间的加载任务
        new Thread(new Runnable() {
            @Override
            public void run() {
                boolean isNotified=false;
                try {
                    int index = 0;                  
                    while (true) {
                        {
                        // 加载数据。。。。
                        }
                        // 加载第一条记录后,唤醒等待线程
                        if (1 == ++index) {
                            synchronized (notifier){
                                notifier.notify();
                                isNotified=true;
                            }                           
                        }
                    }
                } finally {
                    //循环结束,不论有没有加载到数据记录,都执行唤醒,
                    //以确保notify无论如何都会被执行一次,否则等待线程会一直阻塞 
                    synchronized (notifier){
                        if(!isNotified)
                            notifier.notify();
                    }
                }
            }
        }).start();     
        synchronized (notifier){
            try {
                // 启动子线程后立即阻塞, 等待被子线程唤醒
                notifier.wait();
            } catch (InterruptedException e) {}
        }
        // 确保子线程加载了第一记录后,主线程继续自己的工作。。。。
    }

话说,用Object.wait()/notify()倒是挺直观,但是,这synchronized同步块代码写起来实在有点啰嗦,我是个一行代码都不想多写的懒人。 就在想这代码能不能 看着更简单点? 于是想到了java.util.concurrent包下的CountDownLatch, 这是个好东西,顾名思义,它实现了一个多线程环境下倒数计数器锁,当计数器倒数到0时,唤醒阻塞的线程,允许多个线程同时对计数器减1,用在这里实在大材小用了。不过管它呢,方便就成啊。 于是前面的代码就被我用CountDownLatch改造成下面这样:

    public void openSource() {
        // 创建一个初值为1的倒数计数器锁
        CountDownLatch notifier=new CountDownLatch(1);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    int index = 0;
                    while (true) {
                        {
                        // 加载数据。。。。
                        }                       
                        if (1 == ++index) {
                            // 计数减1,唤醒等待线程
                            notifier.countDown();
                        }
                    }
                } finally {
                    // 线程结束之前再执行一次唤醒动作
                    notifier.countDown();
                }
            }
        }).start();
        try {
            // 启动子线程后立即阻塞, 等待被倒数计数器锁唤醒
            notifier.await();
        } catch (InterruptedException e) {}
    }

相比前面的代码,finally{}代码块中没有再判断是否已经执行过唤醒动作,为什么呢,因为countDown当计数已经归0的时候什么也不做,所以就算多执行一次countDown也完全不影响程序的逻辑。 没有烦人的synchronized同步块代码块后,代码是不是看起来简洁一点点呢? 当然相比原始的wait/nofity,CountDownLatch的实现是经过高度封装的代码,最终它也是用wait/nofity来实现的,但逻辑更复杂,所以性能上有多少影响我并不清楚,因为我的应用环境是在程序初始化的时候,并不是高频调用,所以我并不太关注这个。