Java并发编程之CyclicBarrier和CountDownLatch

1、CyclicBarrier简介

CyclicBarrier(栅栏):拦截一组线程并使其阻塞,直到其内部的计数器归零,再唤醒所有的阻塞线程继续执行任务。

基础属性

public class CyclicBarrier {
    
    private static class Generation {
        boolean broken = false;
    }

    // 可重入锁
    private final ReentrantLock lock = new ReentrantLock();
    
    // 提供trip.await();使线程阻塞 trip.signalAll(); 唤醒所有阻塞线程等方法
    private final Condition trip = lock.newCondition();
     
    // 计数器归零时,执行的任务
    private final Runnable barrierCommand;
    
	// 阻塞线程数
	private final int parties;
	// 代,一次成功或异常的操作都会使其更新换代,也是CyclicBarrier可循环使用的原因
    private Generation generation = new Generation();
	
	// 根据parties初始化计数器个数
    private int count;

	...

构造器

    public CyclicBarrier(int parties) {
        this(parties, null);
    }
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

参数parties:初始化计数器计量个数count。
参数barrierAction:当计数器count归零时,先执行下barrierAction任务,再唤醒阻塞线程。

主要方法

await():计数器count > 0,使调用线程阻塞;= 0,唤醒所有阻塞线程。

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); 
        }
    }
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

最终调用的dowait()方法

 private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException, TimeoutException {
        // 可重入锁 ReentrantLock  
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 当前代
            final Generation g = generation;

			// 检查当前broken是否损坏,一组线程中有等待超时、中断或者reset重置
            if (g.broken)
                throw new BrokenBarrierException();

			// 当前线程是否中断
            if (Thread.interrupted()) {
            	// 将broken状态设置为true
            	// 重置计数器count
            	// 唤醒线程
                breakBarrier();
                throw new InterruptedException();
            }

			// 计数器减一
            int index = --count;
            
            // 计数器为零
            if (index == 0) {  
                boolean ranAction = false;
                try {
                	// 构造器有带任务则先执行任务动作
                    final Runnable command = barrierCommand;
                    if (command != null) command.run();
                    
                    // 任务正常,没有抛出异常    
                    ranAction = true;
                    // 重置count
	                // 唤醒线程
	                // 更新换代,generation = new Generation();
                    nextGeneration();
                    return 0;
                } finally {
                	// 任务异常
                    if (!ranAction) breakBarrier();
                }
            }

            // 计数器不为0,循环
            for (;;) {
                try {
                	// 等待时间限制
                    if (!timed) trip.await();
                    else if (nanos > 0L) nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

				// broken是否损坏
                if (g.broken) throw new BrokenBarrierException();

				// 计数器为零的时候,会创建新的generation,使新旧不一致,以此跳出循环
                if (g != generation) return index;

				// 等待超时
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

示例 1 核心构造器

    public static void demo01() throws Exception {
        CyclicBarrier barrier = new CyclicBarrier(4);

        Thread t1 = new Thread(getRunnable(barrier), "t1");
        Thread t2 = new Thread(getRunnable(barrier), "t2");
        Thread t3 = new Thread(getRunnable(barrier), "t3");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(3000);
        System.out.println("所有人准备完毕,开始鸣枪!");
        barrier.await();
    }

    private static Runnable getRunnable(CyclicBarrier barrier) {
        return () -> {
            String thName = Thread.currentThread().getName();
            try {
                System.out.println("选手:" + thName + "准备就绪");
                barrier.await();
                long start = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("选手:" + thName + "跑完全程耗时:" + (System.currentTimeMillis() - start));
            } catch (BrokenBarrierException e) {
                System.out.println(thName + " broken");
            } catch (InterruptedException e) {
                System.out.println(thName + " 线程中断");
            }
        };
    }

Java并发编程之CyclicBarrier和CountDownLatch_第1张图片

示例 2 任务构造器

    public static void demo02() throws Exception {
        CyclicBarrier barrier = new CyclicBarrier(4, () -> {
            try {
                System.out.println(Thread.currentThread().getName() + " 准备鸣枪!");
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " 开始鸣枪!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t1 = new Thread(getRunnable2(barrier), "t1");
        Thread t2 = new Thread(getRunnable2(barrier), "t2");
        Thread t3 = new Thread(getRunnable2(barrier), "t3");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(2000);
        barrier.await();
        System.out.println("计数器归零");
    }

    private static Runnable getRunnable2(CyclicBarrier barrier) {
        return () -> {
            String thName = Thread.currentThread().getName();
            try {
                System.out.println("选手:" + thName + "准备就绪");
                barrier.await();
                long start = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("选手:" + thName + "跑完全程耗时:" + (System.currentTimeMillis() - start));
            } catch (BrokenBarrierException e) {
                System.out.println(thName + " broken");
            } catch (InterruptedException e) {
                System.out.println(thName + " 线程中断");
            }
        };
    }

Java并发编程之CyclicBarrier和CountDownLatch_第2张图片

异常

示例 3 TimeoutException

await():设置参数时,当前线程阻塞时间超过该参数时间,则会抛出TimeoutException异常

    public static void demo03() throws Exception {
        CyclicBarrier barrier = new CyclicBarrier(4);

        Thread t1 = new Thread(getRunnable3(barrier), "t1");
        Thread t2 = new Thread(getRunnable3(barrier), "t2");
        Thread t3 = new Thread(getRunnable3(barrier), "t3");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(5000);
        System.out.println("所有人准备完毕,开始鸣枪!");
        barrier.await();
    }

    private static Runnable getRunnable3(CyclicBarrier barrier) {
        return () -> {
            String thName = Thread.currentThread().getName();
            try {
                System.out.println("选手:" + thName + "准备就绪");
                if ("t2".equals(Thread.currentThread().getName())) {
                    barrier.await(3000, TimeUnit.MILLISECONDS);
                } else {
                    barrier.await();
                }

                long start = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("选手:" + thName + "跑完全程耗时:" + (System.currentTimeMillis() - start));
            } catch (BrokenBarrierException e) {
                System.out.println(thName + " broken损坏");
            } catch (InterruptedException e) {
                System.out.println(thName + " 线程中断");
            } catch (TimeoutException e) {
                System.out.println(thName + " 等待超时");
            }
        };
    }

Java并发编程之CyclicBarrier和CountDownLatch_第3张图片

示例 4 InterruptedException

interrupt():线程中断,抛出InterruptedException异常

    public static void demo04() throws Exception {
        CyclicBarrier barrier = new CyclicBarrier(4);

        Thread t1 = new Thread(getRunnable4(barrier), "t1");
        Thread t2 = new Thread(getRunnable4(barrier), "t2");
        Thread t3 = new Thread(getRunnable4(barrier), "t3");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(1000);
        t2.interrupt();

        Thread.sleep(5000);
        System.out.println("所有人准备完毕,开始鸣枪!");
        barrier.await();
    }

    private static Runnable getRunnable4(CyclicBarrier barrier) {
        return () -> {
            String thName = Thread.currentThread().getName();
            try {
                System.out.println("选手:" + thName + "准备就绪");
                barrier.await();
                long start = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("选手:" + thName + "跑完全程耗时:" + (System.currentTimeMillis() - start));
            } catch (BrokenBarrierException e) {
                System.out.println(thName + " broken损坏");
            } catch (InterruptedException e) {
                System.out.println(thName + " 线程中断");
            }
        };
    }

Java并发编程之CyclicBarrier和CountDownLatch_第4张图片

示例 5 BrokenBarrierException

reset():更新换代,重置前后调用await的线程处于不同的代

    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 将broken状态设置为true
        	// 重置count
        	// 唤醒线程
            breakBarrier();
            // 更新换代   
            nextGeneration(); 
        } finally {
            lock.unlock();
        }
    }
    public static void demo05() throws Exception {
        CyclicBarrier barrier = new CyclicBarrier(4);
        
        Thread t1 = new Thread(getRunnable5(barrier), "t1");
        Thread t2 = new Thread(getRunnable5(barrier), "t2");
        Thread t3 = new Thread(getRunnable5(barrier), "t3");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(5000);
        System.out.println("重置、更新换代");
        barrier.reset();
        System.out.println("所有人准备完毕,开始鸣枪!");
        barrier.await();
    }

    private static Runnable getRunnable5(CyclicBarrier barrier) {
        return () -> {
            String thName = Thread.currentThread().getName();
            try {
                System.out.println("选手:" + thName + "准备就绪");
                barrier.await();
                long start = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("选手:" + thName + "跑完全程耗时:" + (System.currentTimeMillis() - start));
            } catch (BrokenBarrierException e) {
                System.out.println(thName + " broken损坏");
            } catch (InterruptedException e) {
                System.out.println(thName + " 线程中断");
            }
        };
    }

Java并发编程之CyclicBarrier和CountDownLatch_第5张图片
修改重置位置

    public static void demo05() throws Exception {
        CyclicBarrier barrier = new CyclicBarrier(4);

        Thread t1 = new Thread(getRunnable5(barrier), "t1");
        Thread t2 = new Thread(getRunnable5(barrier), "t2");
        Thread t3 = new Thread(getRunnable5(barrier), "t3");

        t1.start();
        t2.start();
        t3.start();


        Thread.sleep(5000);
        //System.out.println("重置、更新换代");
        //barrier.reset();
        Thread t4 = new Thread(getRunnable5(barrier), "t4");
        t4.start();
        System.out.println("所有人准备完毕,开始鸣枪!");
        barrier.await();
    }

    private static Runnable getRunnable5(CyclicBarrier barrier) {
        return () -> {
            String thName = Thread.currentThread().getName();
            try {
                System.out.println("选手:" + thName + "准备就绪");
                // 重置
                if ("t2".equals(thName)) {
                    barrier.reset();
                }
                barrier.await();
                long start = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("选手:" + thName + "跑完全程耗时:" + (System.currentTimeMillis() - start));
            } catch (BrokenBarrierException e) {
                System.out.println(thName + " broken损坏");
            } catch (InterruptedException e) {
                System.out.println(thName + " 线程中断");
            }
        };
    }

Java并发编程之CyclicBarrier和CountDownLatch_第6张图片

2、CountDownLatch简介

CountDownLatch:是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信作用。
CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。
内部有计数器,计数器初始值为线程的数量。每当一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

主要方法说明

countDown:使CountDownLatch内部计数器减一,当计数器归零时,唤醒线程

    public void countDown() {
        sync.releaseShared(1);
    }

await:使当前线程在计数器归零前一直等待,除非线程被中断或超出了指定的等待时间。

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

示例 1

    private static void demo_01() {
        final CountDownLatch latch = new CountDownLatch(3);

        Thread t1 = new Thread(getRunnable(latch), "t1");
        Thread t2 = new Thread(getRunnable(latch), "t2");
        Thread t3 = new Thread(getRunnable(latch), "t3");

        t1.start();
        t2.start();
        t3.start();

        try {
            latch.await();//阻塞当前线程,直到计数器归零
            System.out.println("所有参赛人员已完成比赛,下面进行统计");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static Runnable getRunnable(CountDownLatch latch) {
        return () -> {
            String thName = Thread.currentThread().getName();
            try {
                System.out.println("选手:" + thName + "准备就绪");
                long start = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("选手:" + thName + "跑完全程耗时:" + (System.currentTimeMillis() - start));
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
    }

Java并发编程之CyclicBarrier和CountDownLatch_第7张图片

示例 2

    private static void demo_02() {
        final CountDownLatch referee = new CountDownLatch(1);
        final CountDownLatch players = new CountDownLatch(4);

        Thread t1 = new Thread(getRunnable2(referee, players), "t1");
        Thread t2 = new Thread(getRunnable2(referee, players), "t2");
        Thread t3 = new Thread(getRunnable2(referee, players), "t3");
        Thread t4 = new Thread(getRunnable2(referee, players), "t4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        try {
            Thread.sleep(3000);
            System.out.println("裁判:" + Thread.currentThread().getName() + " 开始鸣枪!");
            referee.countDown();
            players.await();
            System.out.println("比赛结束");
            System.out.println("裁判:" + Thread.currentThread().getName() + "汇总成绩排名");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static Runnable getRunnable2(CountDownLatch referee, CountDownLatch players) {
        return () -> {
            String thName = Thread.currentThread().getName();
            try {
                System.out.println("选手:" + thName + " 准备就绪");
                referee.await();
                System.out.println("选手:" + thName + " 鸣枪开跑");
                long start = System.currentTimeMillis();
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println("选手:" + thName + " 到达终点耗时:" + (System.currentTimeMillis() - start));
                players.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
    }

Java并发编程之CyclicBarrier和CountDownLatch_第8张图片

3、CyclicBarrier和CountDownLatch的区别

CyclicBarrier是可重复使用的,CountDownLatch是一次性的;
CyclicBarrier调用方法await就可以进行减一和线程阻塞操作,CountDownLatch通过方法countDown减一和方法await阻塞线程;
CyclicBarrier计数器归零时可以执行额外任务,CountDownLatch不可以;

你可能感兴趣的:(多线程,并发编程,多线程,并发编程)