11.countDownLatch(共享锁)

275 阅读3分钟
# countDownLatch(共享锁)

CountDownLatch是Java并发包下的一个工具类,latch是门闩的意思。

顾名思义,CountDownLatch就是有一个门闩挡住了里面的人(线程)出来,当count减到0的时候,门闩就打开了,人(线程)就可以出来了。

API

//CountDownLatch类只提供了一个构造器:参数count为计数值即state
//
public CountDownLatch(int count) {  
};  
​
//下面这3个方法是CountDownLatch类中最重要的方法:
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
//可以多次调用 可以多次调用 可以多次调用 用于阻塞多个线程
public void await() throws InterruptedException { };   
​
//将count值减1
//可以多次调用 可以多次调用 可以多次调用 用于减少构造器中的count值
public void countDown() { }; 
​
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  

实现原理

1.创建计数器

当我们调用CountDownLatch countDownLatch=new CountDownLatch(4) 时候,此时会创建一个AQS的同步队列,并把创建CountDownLatch 传进来的count赋值给AQS队列的 state,所以state的值也代表CountDownLatch所剩余的计数次数。

2.阻塞线程

当我们调用countDownLatch.wait()的时候,会创建一个节点,加入到AQS阻塞队列,并同时把当前线程(主线程)挂起。

判断计数器是计数完毕,未完毕则把当前线程加入阻塞队列,挂起当前线程。

3.计数器递减

当我们调用countDownLatch.down()方法的时候,会对计数器进行减1操作,AQS内部是通过释放锁的方式,对state进行减1操作,当state=0的时候证明计数器已经递减完毕,此时会将AQS阻塞队列里的节点线程全部唤醒。

应用:并发等待

不要以为countDownLatch只能await一次。他可以await多次!

比如2个线程t3 t4 同时等待t1 t2完成。此时就可以利用CountDownLatch来实现这种功能了。

package org.apache.rocketmq.example.thread;
​
import java.util.concurrent.CountDownLatch;
​
public class Test {
    public static void main(String[] args) {
        //指定count=2
        final CountDownLatch latch = new CountDownLatch(2);
​
        Thread t1 = new Thread() {
            public void run() {
                try {
               System.out.println("子线程" + Thread.currentThread().getName() + "正在执行");
                    Thread.sleep(3000);
               System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕");
                    //count减少1
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
​
            ;
        };
        t1.start();
​
        Thread t2 = new Thread() {
            public void run() {
                try {
               System.out.println("子线程" + Thread.currentThread().getName() + "正在执行");
                    Thread.sleep(3000);
               System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕");
                    //count减少1
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
​
            ;
        };
        t2.start();
​
​
        Runnable t3 = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("t3等待中");
                    //当count=0 会被唤醒
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3等待完成");
            }
        };
        new Thread(t3).start();
​
​
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("t4等待中");
                    //当count=0 会被唤醒
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t4等待完成");
            }
        });
        t4.start();
        System.out.println("执行主线程");
    }
}
子线程Thread-0正在执行
子线程Thread-1正在执行
t3等待中
t4等待中
子线程Thread-0执行完毕
子线程Thread-1执行完毕
t3等待完成
t4等待完成

应用:压力测试

package com.xttblog.canal.test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * CasSingletonTest
 * @author www.xttblog.com
 * @date 2019/2/27 下午2:39
 */
public class CasSingletonTest {
    public static AtomicInteger objectcount = new AtomicInteger();
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch begin = new CountDownLatch(1);
        final CountDownLatch last = new CountDownLatch(1000);
        for(int i=0;i<1000;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //1.所有的线程都会阻塞在这
                        begin.await();
                        System.out.println
                            (Thread.currentThread().getName()+":begin...");
                        //3.阻塞的1000个线程并发执行
                        Singleton sba = Singleton.getInstance();
                        System.out.println(Thread.currentThread().getName()+":OK");
                        //4.释放门栓
                        last.countDown();
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }).start();
        }
        //2.释放门栓
        begin.countDown();
        //5.等待1000个线程执行完毕 
        last.await();
        System.out.println("new objects: "+objectcount.get());
    }
}

参考:《CountDownLatch 压测教程