Java多线程系列9(CountDownLatch)

来源:互联网 发布:淘宝卖家手册pdf 编辑:程序博客网 时间:2024/06/08 04:59

1 CounDownLatch介绍

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

//递减锁存器的计数值,如果计数达到0,就释放所有等待的线程。如果计数大于0,计数减1public void countDown()//等待锁存器的计数值减为0public 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 arrivalPeople 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 tripStaff index = 3  begin wait business tripStaff index = 4  begin wait business tripStaff index = 2  begin wait business tripStaff index = 5  begin wait business tripStaff index = 6  begin wait business tripStaff index = 7  begin wait business tripStaff index = 8  begin wait business tripStaff index = 9  begin wait business tripStaff index = 10  begin wait business tripManager agree the business tripLeader agree the business tripStaff index = 3 can go nowStaff index = 1 can go nowStaff index = 4 can go nowStaff index = 5 can go nowStaff index = 2 can go nowStaff index = 7 can go nowStaff index = 8 can go nowStaff index = 9 can go nowStaff index = 10 can go nowStaff index = 6 can go now

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

阅读全文
0 0
原创粉丝点击