CountDownLatch
最近在看zookeeper的时候看到了zookeeper在连接实例化的时候需要等zookeeper准备就绪后再继续执行(实现方式是使用CountDownLatch,因此对CountDownLatch总结一下)。
//zookeeper connect
public class ConnectionWatch implements Watcher {
private static final int SESSION_TIMEOUT = 5000;
protected ZooKeeper zk;
private CountDownLatch connectedSignal = new CountDownLatch(1);
public void connect(String hosts) throws IOException, InterruptedException {
//第一个参数是Zookeeper服务主机地址,可指定端口号,默认为2181;第二个参数以毫秒为单位的会话超时参数;
// 第三个参数是一个Watcher对象的实例。Watcher对象接收来自于Zookeeper的回调,以获得各种事件通知,
// 本例中CreateGroup是一个Watcher对象,因此参数为this
zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
//当一个ZooKeeper的实例被创建时,会启动一个线程连接到Zookeeper服务。
// 由于对构造函数的调用是立即返回的,因此在使用新建的Zookeeper对象之前一定要等待其与Zookeeper服务之间的连接建立成功。
// 使用CountDownLatch使当前线程等待,直到Zookeeper对象准备就绪
System.out.println("await......");
connectedSignal.await();
System.out.println("主线程继续执行");
}
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
System.out.println("zookeeper准备就绪");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
connectedSignal.countDown();
}
}
public void close() throws InterruptedException {
zk.close();
}
}
CountDownLatch是什么
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,例如,应用程序的主线程希望在负责启动框架服务的线程启动所有框架服务之后再执行。
CountDownLatch(通过CountDownLatch.await())能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一(通过 CountDownLatch.countDown())。当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务。
在这里看起来是不是跟Thread.join()有点类似,在下面会对两者进行比较。
CountDownLatch作用
-
实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
-
开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
-
死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
CountDownLatch的不足
CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
CountDownLatch与Thread.join()的区别
在使用上,CountDownLatch和join都有主线程等待子线程执行完再执行的意思。但是他们两之间还是有些差别。
join:在当前线程中,如果调用某个thread的join方法,那么当前线程就会被阻塞,直到thread线程执行完毕,当前线程才能继续执行。join的原理是,不断的检查thread是否存活,如果存活,那么让当前线程一直wait,直到thread线程终止,线程的this.notifyAll 就会被调用。
CountDownLatch:在CountDownLatch中我们主要用到两个方法一个是await()方法,调用这个方法的线程会被阻塞,另外一个是countDown()方法,调用这个方法会使计数器减一,当计数器的值为0时,因调用await()方法被阻塞的线程会被唤醒,继续执行(同时CountDownLatch需要一个起始量count,new CountDownLatch(int count))。
例子如下:
/**
* join 做不到子线程执行到一半就允许主线程继续执行
*/
public class CountDownLatchAndJoin {
public static void main(String[] args) {
// System.out.println("--------------test join----------------");
// testJoin();
System.out.println("--------------test countDownLatch----------------");
testCountDownLatch();
}
public static void testCountDownLatch(){
CountDownLatch countDownLatch=new CountDownLatch(3);
System.out.println("----------------模拟join-----------------------");
CountDownLatchThread countDownLatchThreadA=new CountDownLatchThread("countDownLatchThreadA",countDownLatch);
CountDownLatchThread countDownLatchThreadB=new CountDownLatchThread("countDownLatchThreadB",countDownLatch);
CountDownLatchThread countDownLatchThreadC=new CountDownLatchThread("countDownLatchThreadC",countDownLatch);
countDownLatchThreadA.start();
countDownLatchThreadB.start();
countDownLatchThreadC.start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----------------模拟join结束"+new Date()+"-----------------------");
CountDownLatch countDownLatch2=new CountDownLatch(3);
System.out.println("----------------在子线程执行到一半就countdown-----------------------");
CountDownLatchThread2 countDownLatchThread2A=new CountDownLatchThread2("countDownLatchThread2A",countDownLatch2);
CountDownLatchThread2 countDownLatchThread2B=new CountDownLatchThread2("countDownLatchThread2B",countDownLatch2);
CountDownLatchThread2 countDownLatchThread2C=new CountDownLatchThread2("countDownLatchThread2C",countDownLatch2);
countDownLatchThread2A.start();
countDownLatchThread2B.start();
countDownLatchThread2C.start();
try {
countDownLatch2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----------------在子线程执行到一半就countdown结束"+new Date()+"-----------------------");
}
public static void testJoin(){
System.out.println("------------不加join----------------");
JoinThread joinThreadA=new JoinThread("joinThreadA");
JoinThread joinThreadB=new JoinThread("joinThreadB");
JoinThread joinThreadC=new JoinThread("joinThreadC");
joinThreadA.start();
joinThreadB.start();
joinThreadC.start();
//等上面的例子执行完再执行下面的例子
try {
joinThreadA.join();
joinThreadB.join();
joinThreadC.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------------加join----------------");
JoinThread joinThread2A=new JoinThread("joinThread2A");
JoinThread joinThread2B=new JoinThread("joinThread2B");
JoinThread joinThread2C=new JoinThread("joinThread2C");
joinThread2A.start();
joinThread2B.start();
//等待joinThread2B线程执行完再执行下面语句
try {
joinThread2B.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
joinThread2C.start();
}
}
class JoinThread extends Thread{
private String name;
public JoinThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name+" is start in "+new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+" is over in "+new Date());
}
}
class CountDownLatchThread extends Thread{
private String name;
private CountDownLatch countDownLatch;
public CountDownLatchThread(String name,CountDownLatch countDownLatch) {
this.name = name;
this.countDownLatch=countDownLatch;
}
@Override
public void run() {
System.out.println(name+" is start in "+new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+" is over in "+new Date());
countDownLatch.countDown();
}
}
class CountDownLatchThread2 extends Thread{
private String name;
private CountDownLatch countDownLatch;
public CountDownLatchThread2(String name,CountDownLatch countDownLatch) {
this.name = name;
this.countDownLatch=countDownLatch;
}
@Override
public void run() {
System.out.println(name+" is start in "+new Date());
//执行一半就可以继续执行主线程,join是做不到这种操作
try {
Thread.sleep(1000);
countDownLatch.countDown();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+" is over in "+new Date());
}
}