CountDownLatch解析和应用示例

前言

在日常处理线程同步问题的时候我们经常联想到的可能有下面几种办法:
1.synchronized关键字
2.Java5引入的java.util.concurrent.locks包的显示锁
3.CountDownLatch

今天要说的CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

简单的来说,CountDownLatch就是可以通过这种计数的方式控制线程的执行临界区,举个例子:
我有事务A、B、C、D需要完成,A、B、C是D的前置条件,D事务必须要等A、B、C全部完成后才能开始。

我们就可以通过CountDownLatch去管理这种执行上的先后问题。

实现

打开java.util.concurrent包的CountDownLatch类,可以看到有以下成员方法。
CountDownLatch解析和应用示例_第1张图片

几个关键的方法:
- CountDownLatch(int)
初始化一个CountDownLatch,设置闩值,如果闩值未减到0,不执行后续语句
- await()
判断CountDownLatch的值是否已经减到了0,没有的话将一直处于等待状态
- countDown()
供线程完成自己的业务后调用,CountDownLatch初始化的值减1
- getCount()
获取当前CountDownLatch的值

看了CountDownLatch具体的实现方法,关于前言中的例子如何实现,是不是就已经胸有成竹了,我们只需要做下面几件事。

1.为A、B、C三个事务各声明一个线程去执行相关业务代码。
2.声明一个初始值为3的CountDownLatch,用来计数。
3.A、B、C三个事务的线程在执行完成后调用countDown()方法。
4.通过CountDownLatch的await()方法判断A、B、C是否均已完成。

示例

针对上面的例子,模拟了一个应用场景。
假设某个新上线项目的部署需要先启动网络服务、DNS服务以及数据服务,在其他三个服务启动前,部署事务一直处于闭锁状态。

  • Service.java
    服务类,声明3个服务对象。
package countDownLatch.service;

/**
 * 
 * @author lenovo
 * 模拟服务启动
 */
public class Service {
    //服务名
    private String serviceName;
    //服务启动所需时间
    private Long startUpMills;

    public Service(String serviceName, Long startUpMills){
        this.serviceName = serviceName;
        this.startUpMills = startUpMills;
    }

    public void startUpService(){
        try{
            System.out.println("服务"+serviceName+"启动中...");
            Thread.sleep(startUpMills);
        }catch(InterruptedException ie){
            ie.printStackTrace();
        }
        System.out.println("服务"+serviceName+"启动完毕...");
    }

}
  • ServiceStartupTestThread.java
    服务线程类,将每个服务的启动工作交付给线程执行,并完成服务启动前后的相关操作。
package countDownLatch.service;

import java.util.concurrent.CountDownLatch;

public class ServiceStartupTestThread implements Runnable{
    //当前线程内的服务对象
    private Service service;
    //countDownLatch计数器
    private CountDownLatch cdl;

    ServiceStartupTestThread(Service service, CountDownLatch cdl){
        this.service = service;
        this.cdl = cdl;
    }

    @Override
    public void run() {
        //处理线程绑定的业务逻辑
        service.startUpService();
        //处理完毕CountDownLatch-1
        cdl.countDown();
    }
}
  • StartupProject.java
    启动项目主方法,在这里判断三个服务是否均已启动完毕。
package countDownLatch.service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class StartupProject {
    public static void main(String[] args) {
        CountDownLatch cdl = new CountDownLatch(3);

        //创建服务对象
        Service netWork = new Service("网络服务", (long)(Math.random()*5000+1000));
        Service dns = new Service("DNS服务", (long)(Math.random()*5000+1000));
        Service dataSource = new Service("数据服务", (long)(Math.random()*5000+1000));
        //为每一个服务提供执行线程
        ServiceStartupTestThread netWorkThread = new ServiceStartupTestThread(netWork, cdl);
        ServiceStartupTestThread dnsThread = new ServiceStartupTestThread(dns, cdl);
        ServiceStartupTestThread dataSourceThread = new ServiceStartupTestThread(dataSource, cdl);

        //创建线程list
        List serviceList = new ArrayList();
        serviceList.add(netWorkThread);
        serviceList.add(dnsThread);
        serviceList.add(dataSourceThread);

        //创建一个容量适配的连接池
        ExecutorService executor = Executors.newFixedThreadPool(serviceList.size());
        for(ServiceStartupTestThread t : serviceList){
            executor.execute(t);
        }

        //关闭连接池
        executor.shutdown();

        try {
            cdl.await();
            System.out.println("所有服务已经启动完毕!");
            //method()启动项目部署...
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
CountDownLatch解析和应用示例_第2张图片

参考文章

感谢!

http://www.importnew.com/15731.html
什么时候使用CountDownLatch

http://blog.csdn.net/jackfrued/article/details/44499227
关于Java并发编程的总结和思考

你可能感兴趣的:(多线程)