Java多线程系列9(CountDownLatch)

12 篇文章 0 订阅

1 CounDownLatch介绍

CountDownLatch是java 1.5新引入的一个同步工具类,常用场景为:一个(或者多个)线程等待另外N个线程完成某个事情之后才能执行。是一个非常有用的线程同步类。
其API有:

//递减锁存器的计数值,如果计数达到0,就释放所有等待的线程。如果计数大于0,计数减1
public void countDown()
//等待锁存器的计数值减为0
public void await()

2实际例子

2.1 场景一

考虑这样一个实际的问题:公司组织一个会议,与会者10名,只有当10名与会者全部到齐,会议才能开始。
这个场景,用CountDownLatch可以很方便实现:

public class TestCountDownLatch {
    private static final int PEOPLE_NUM = 10;
    public static CountDownLatch latch = new CountDownLatch(PEOPLE_NUM);
    private static AtomicInteger index = new AtomicInteger(0);

    public static class People implements Runnable {
        private CountDownLatch latch;
        private int index;

        public People(CountDownLatch latch, int index) {
            this.latch = latch;
            this.index = index;
        }

        @Override
        public void run() {
            latch.countDown();
            System.out.println("People index = " + index + " arrival.");
        }
    }

    public static class Conference implements Runnable {
        private CountDownLatch latch;
        public Conference(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            System.out.println("Conference begin wait all people arrival");
            try {
                latch.await();
            } catch (InterruptedException e) {

            }
            System.out.println("Conference started");
        }
    }

    public static void main(String[] args) {
        Thread conferenceThread = new Thread(new Conference(latch));
        conferenceThread.start();
        for (int i = 0; i < PEOPLE_NUM; i++) {
            Thread thread = new Thread(new People(latch, index.addAndGet(1)));
            thread.start();
        }
    }
}

运行结果:

Conference begin wait all people arrival
People index = 1 arrival.
People index = 2 arrival.
People index = 3 arrival.
People index = 4 arrival.
People index = 5 arrival.
People index = 6 arrival.
People index = 7 arrival.
People index = 8 arrival.
People index = 9 arrival.
People index = 10 arrival.
Conference started

从运行结果可以看到调用await以后,Conference会等待其余10个线程调用countDown,当计数为0时,Conference会从 await点继续向下执行。

Android系统中很多地方都用到了这样的设计。
比如SharedPreferences.java中:

         public boolean commit() {
            MemoryCommitResult mcr = commitToMemory();
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            try {
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException e) {
                return false;
            }
            notifyListeners(mcr);
            return mcr.writeToDiskResult;
        }
.....省略无关代码
        public void setDiskWriteResult(boolean result) {
            writeToDiskResult = result;
            writtenToDiskLatch.countDown();
        }

实现commit()同步方法的时候,会调用await等待写执行,写入磁盘的操作完成时,会调用countDown(),这样就保证commit()返回的时候,数据已经被写入磁盘。从而保证数据不会出现问题。

2.2 场景二

考虑这样一个场景:Android执行代码时,如何将一段异步代码转为同步代码?
这个场景,用CountDownLatch也能很好的予以解决。Android插件花框架Replugin的一段代码就是这个场景的一个实现:

public class ThreadUtils {

    private static Handler sHandler = new Handler(Looper.getMainLooper());

    /**
     * 确保一定在主线程中使用
     * <p>
     * 若当前处于主线程,则直接调用。若当前处于其它线程,则Post到主线程后等待结果
     *
     * @param callable Callable对象
     * @param wait 最长等待主线程的时间
     * @param <T> 任何Object子类均可以
     * @return 主线程执行完方法后,返回的结果
     */
    public static <T> T syncToMainThread(final Callable<T> callable, int wait) throws Throwable {
        if (sHandler.getLooper() == Looper.myLooper()) {
            // 已在UI线程中使用,则直接调用它
            return callable.call();

        } else {
            // 不在UI线程,需尝试Post到UI线程并等待
            return syncToMainThreadByOthers(callable, wait);
        }
    }

    private static <T> T syncToMainThreadByOthers(final Callable<T> callable, int wait) throws Throwable {
        final AtomicReference<T> result = new AtomicReference<>();
        final AtomicReference<Throwable> ex = new AtomicReference<>();

        // 异步转同步
        final CountDownLatch latch = new CountDownLatch(1);

        // 必须在主线程进行
        sHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    result.set(callable.call());
                } catch (Throwable e) {
                    ex.set(e);
                }
                latch.countDown();
            }
        });

        try {
            latch.await(wait, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            // ignore
        }

        // 若方法体有异常?直接抛出
        Throwable exo = ex.get();
        if (exo != null) {
            throw exo;
        }

        // 没有问题?则直接返回结果
        return result.get();
    }
}

syncToMainThreadByOthers中的实现,可以看到如何将一段代码由异步转为同步来执行。

2.3 场景三

以上都是一个线程等待N个线程执行完毕。CountDownLatch也可以适用于多个线程等待N个线程的场景。
比如小组的员工出差,需要组长审批,总监审批完毕以后,才可以出差。用CountDownLatch也能很好的予以解决:
代码如下:

 public class TestCountDownLatch {
    private static final int ROUTINE_NUM = 2;
    private static final int STAFF_NUM = 10;
    public static CountDownLatch latch = new CountDownLatch(ROUTINE_NUM);
    private static AtomicInteger index = new AtomicInteger(0);

    public static class Manager implements Runnable {
        private CountDownLatch latch;

        public Manager(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            latch.countDown();
            System.out.println("Manager agree the business trip");
        }
    }

    public static class Leader implements Runnable {
        private CountDownLatch latch;

        public Leader(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            latch.countDown();
            System.out.println("Leader agree the business trip");
        }
    }

    public static class Staff implements Runnable {
        private CountDownLatch latch;
        private int index;
        public Staff(CountDownLatch latch, int index) {
            this.latch = latch;
            this.index = index;
        }

        @Override
        public void run() {
            System.out.println("Staff index = " + index + "  begin wait business trip");
            try {
                latch.await();
            } catch (InterruptedException e) {

            }
            System.out.println("Staff index = " + index + " can go now");
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < STAFF_NUM; i++) {
            Thread thread = new Thread(new Staff(latch, index.addAndGet(1)));
            thread.start();
        }
        Thread manager = new Thread(new Manager(latch));
        manager.start();
        Thread leader = new Thread(new Leader(latch));
        leader.start();
    }
}

测试结果如下:

Staff index = 1  begin wait business trip
Staff index = 3  begin wait business trip
Staff index = 4  begin wait business trip
Staff index = 2  begin wait business trip
Staff index = 5  begin wait business trip
Staff index = 6  begin wait business trip
Staff index = 7  begin wait business trip
Staff index = 8  begin wait business trip
Staff index = 9  begin wait business trip
Staff index = 10  begin wait business trip
Manager agree the business trip
Leader agree the business trip
Staff index = 3 can go now
Staff index = 1 can go now
Staff index = 4 can go now
Staff index = 5 can go now
Staff index = 2 can go now
Staff index = 7 can go now
Staff index = 8 can go now
Staff index = 9 can go now
Staff index = 10 can go now
Staff index = 6 can go now

可以看到Staff 10个线程等待Manager/Leader两个线程审批出差申请,两个审批步骤通过以后,10个等待的Staff员工就可以出差了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值