CountDownLatch/CyclicBarrie用法记录

在jdk1.5中,java提供了很多工具类帮助我们进行并发编程,其中就有CountDownLatch和CyclicBarrie

 

1.CountDownLatch的用法


CountDownLatch 位于 java.util.concurrent 包下,其中最主要的方法就是 两个await方法了, 当我们调用await方法时,当前线程会被挂起,直到count的值为零才继续执行

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

 

我们可以写一个小demo看看CountDownLatch的用法

public class CountDownLatchDemo {
    static SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-DD hh:mm:ss");
    static  CountDownLatch countDownLatch=new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        System.out.println(" 当前线程:"+Thread.currentThread().getName()+" " +
                            "当前时间:"+simpleDateFormat.format(new Date())+"" +
                            "当前计数器:"+countDownLatch.getCount());

        new Thread(new Task(3000)).start();
        new Thread(new Task(5000)).start();

        //等待计数器的值为0
        countDownLatch.await();

        System.out.println(" 当前线程:"+Thread.currentThread().getName()+" " +
                            "当前时间:"+simpleDateFormat.format(new Date())+"" +
                            "当前计数器:"+countDownLatch.getCount());
    }

    static class Task implements Runnable{
        private Integer time;
        public Task(Integer time) {
            this.time=time;
        }
        @Override
        public void run() {
            System.out.println(" 当前线程:"+Thread.currentThread().getName()+" " +
                                "当前时间:"+simpleDateFormat.format(new Date())+"" +
                                "当前计数器:"+countDownLatch.getCount());
            try {
                //模拟业务执行,休眠一段时间
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //执行完毕之后把计数器的值减一
            countDownLatch.countDown();
            System.out.println(" 当前线    程:"+Thread.currentThread().getName()+" " +
                                "当前时间:"+simpleDateFormat.format(new Date())+"" +
                                "当前计数器:"+countDownLatch.getCount());
                    }
    }
}

执行的结果是

当前线程:main 当前时间:2018-01-22 10:22:10当前计数器:2
 当前线程:Thread-0 当前时间:2018-01-22 10:22:10当前计数器:2
 当前线程:Thread-1 当前时间:2018-01-22 10:22:10当前计数器:2
 当前线程:Thread-0 当前时间:2018-01-22 10:22:13当前计数器:1
 当前线程:Thread-1 当前时间:2018-01-22 10:22:15当前计数器:0
 当前线程:main 当前时间:2018-01-22 10:22:15当前计数器:0
View Code

通过CountDownLatch 我们可以用于 某个线程A等到其他线程执行完毕后,它才执行

 

2.CyclicBarrier的用法


 

CyclicBarrier在功能上可能和CountDownLatch有点类似,都有线程挂起的功能,不过CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

我可以看一个小例子

public class CyclicBarrierDemo2 {
    public static void main(String[] args) throws InterruptedException {
        int N=3;
        CyclicBarrier cyclicBarrier=new CyclicBarrier(N);
        for(int i=0;i<N;i++){
            Thread.sleep(i*1000);
            new Thread(new Barrier(cyclicBarrier)).start();
        }
    }
}

class Barrier implements Runnable{
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-DD hh:mm:ss");
    CyclicBarrier cyclicBarrier;
    public Barrier(CyclicBarrier cyclicBarrier){
        this.cyclicBarrier=cyclicBarrier;
    }
    @Override
    public void run() {
        try {
            System.out.println(" 当前线程:"+Thread.currentThread().getName()+" " +
                    "当前时间:"+simpleDateFormat.format(new Date()));
            //模拟业务
            Thread.sleep(3000);
            //等待其他线程到达await状态
            cyclicBarrier.await();
            System.out.println(" 【阻塞完成】当前线程:"+Thread.currentThread().getName()+" " +
                    "当前时间:"+simpleDateFormat.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

以上代码运行结果是

当前线程:Thread-0 当前时间:2018-01-22 10:56:51
 当前线程:Thread-1 当前时间:2018-01-22 10:56:52
 当前线程:Thread-2 当前时间:2018-01-22 10:56:54
 【阻塞完成】当前线程:Thread-2 当前时间:2018-01-22 10:56:57
 【阻塞完成】当前线程:Thread-0 当前时间:2018-01-22 10:56:57
 【阻塞完成】当前线程:Thread-1 当前时间:2018-01-22 10:56:57
View Code

可以看到3个线程在运行到 cyclicBarrier.await() 时,线程会处于barrier状态,同时会检查其他线程是否也处于barrier 状态了,如果大家都处于barrier状态了,所有线程一起往下接着运行

CyclicBarrie还有一个构造方法是

public CyclicBarrier(int parties, Runnable barrierAction) {}

作用是当子线程都处于barrier状态了,会任意挑选一个线程来执行这个Runnable线程,我们可以把以上案例改造下

CyclicBarrier cyclicBarrier=new CyclicBarrier(N, new Runnable() {
            @Override
            public void run() {
                System.out.println("【主线程执行】当前线程:"+Thread.currentThread().getName()+" " +
                        "当前时间:"+simpleDateFormat.format(new Date()));
            }
        });

执行的结果就是

当前线程:Thread-0 当前时间:2018-01-22 11:01:05
 当前线程:Thread-1 当前时间:2018-01-22 11:01:06
 当前线程:Thread-2 当前时间:2018-01-22 11:01:08
【主线程执行】当前线程:Thread-2 当前时间:2018-01-22 11:01:11
 【阻塞完成】当前线程:Thread-2 当前时间:2018-01-22 11:01:11
 【阻塞完成】当前线程:Thread-0 当前时间:2018-01-22 11:01:11
 【阻塞完成】当前线程:Thread-1 当前时间:2018-01-22 11:01:11
View Code

 


 

CountDownLatch和CyclicBarrie还有个不同是CyclicBarrie可以重用而CountDownLatch却不可以。

这个其实也很容易理解,CountDownLatch需要不停的减到零才阻塞,这就相当于是个计数器

而CyclicBarrie 却像一个开关,每次都处于barrier 开关打开。

 

CountDownLatch的内部实现


 先从构造函数开始,CountDownLatch的内部也有一个Sync内部类

Sync 继承了AQS,可见AQS真是并发包中大爸爸,本次为什么想要了解CountDownLatch的内部实现呢,其实也是想借此了解下AQS中共享锁的实现,顺便再次膜拜下Doug Lea

先从state开始,可以看到和独占锁一样,AQS同样有个state的变量用于控制node节点能否获取到锁。

 

再看下await方法

再看下acquireSharedInterruptibly方法

1.前两行会检查下线程是否被打断

2.尝试着获取共享锁,小于0,表示获取失败,如果失败会将当前线程放在队列中

这一行就是CountDownLatch的核心,如果CountDownLatch的值大于0,线程会一直阻塞,因为线程在获取共享锁的时候必然会失败。

 doAcquireSharedInterruptibly 这个方法应该是把线程放到队列里了

 

一直到这,我还没找到共享锁的核心在哪呢,再看下 setHeadAndPropagate 内部

 

 

当线程被唤醒后,会重新尝试获取共享锁,而对于CountDownLatch线程获取共享锁判断依据是state是否为0,而这个时候显然state已经变成了0,因此可以顺利获取共享锁并且依次唤醒AQS队里中后面的节点及对应的线程。

 

最后总结下

在获取时,维护了一个sync队列,每个节点都是一个线程在进行自旋,而依据就是自己是否是首节点的后继并且能够获取资源;
在释放时,仅仅需要将资源还回去,然后通知一下后继节点并将其唤醒。
这里需要注意,队列的维护(首节点的更换)是依靠消费者(获取时)来完成的,也就是说在满足了自旋退出的条件时的一刻,这个节点就会被设置成为首节点。

 

1. CountDownLatch内部有count计数器
2.count的计数器为0,线程才能获取锁
3.否则的的话就会进入到队列中去
4.一旦减到0,队列的第一个节点就会尝试获取锁
5.获取成功后会唤醒下一个节点,让他去尝试获取锁,以此不断的往下延续

参考:http://www.infoq.com/cn/articles/java8-abstractqueuedsynchronizer

posted @ 2018-01-22 11:26  XuMinzhe  阅读(259)  评论(0编辑  收藏  举报