CounDownLatch、CyclicBarrier、Semaphore


CountDownLatch
CountDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。

CountDownLatch概念

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

CountDownLatch的用法

CountDownLatch典型用法:1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

CountDownLatch典型用法:2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。

// 当一个任务需要多任务同时进行,提高效率,缩短串行执行时间。cdl.await最后等待位置
public static void main(String[] args) {
    CountDownLatch cdl = new CountDownLatch(3);
    ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>());

    for (int i = 0; i < 3; i++) {
        executor.execute(() -> {cdl.countDown(); System.out.println(String.valueOf(1111));});
    }

    try {
        cdl.await();
        System.out.println("线程结束");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    executor.shutdown();
}

CyclicBarrier

CyclicBarrier栅栏类似于赛跑同一起跑线
public static void main(String[] args) {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
        System.out.println("栅栏中的线程执行完成");
    });
    ExecutorService executorService = Executors.newFixedThreadPool(2);

    executorService.submit(() -> {
        try {
            cyclicBarrier.await();
            System.out.println("线程1:" + Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    });

    executorService.submit(() -> {
        try {
            cyclicBarrier.await();
            System.out.println("线程2:" + Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    });

    executorService.shutdown();
}

例子2

public class TravelTask implements Runnable{

    private final CyclicBarrier cyclicBarrier;
    private final String name;
    /**
     * 赶到的时间
     */
    private final int arriveTime;

    public TravelTask(CyclicBarrier cyclicBarrier, String name, int arriveTime) {
        this.cyclicBarrier = cyclicBarrier;
        this.name = name;
        this.arriveTime = arriveTime;
    }

    @Override
    public void run() {
        try {
            //模拟达到需要花的时间
            Thread.sleep(arriveTime * 1000L);
            System.out.println(name +"到达集合点");
            cyclicBarrier.await();
            System.out.println(name +"开始旅行啦~~");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

public class TourGuideTask implements Runnable{
    @Override
    public void run() {
        System.out.println("****导游分发护照签证 开始****");
        try {
            //模拟发护照签证需要2秒
            Thread.sleep(2000);
            System.out.println("****导游分发护照签证 结束****");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class TestClient {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3,new TourGuideTask());
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        //登哥最大牌,到的最晚
        fixedThreadPool.execute(new TravelTask(cyclicBarrier,"哈登",5));
        fixedThreadPool.execute(new TravelTask(cyclicBarrier,"保罗",3));
        fixedThreadPool.execute(new TravelTask(cyclicBarrier,"戈登",1));

        fixedThreadPool.shutdown();
    }
}

例子3

public class Horse implements Runnable {

    private static int counter = 0;
    private final int id = counter++;
    private int strides = 0;
    private static Random rand = new Random(47);
    private static CyclicBarrier barrier;

    public Horse(CyclicBarrier b) { barrier = b; }

    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                synchronized(this) {
                    //赛马每次随机跑几步
                    strides += rand.nextInt(3);
                }
                barrier.await();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public String tracks() {
        StringBuilder s = new StringBuilder();
        for(int i = 0; i < getStrides(); i++) {
            s.append("*");
        }
        s.append(id);
        return s.toString();
    }

    public synchronized int getStrides() { return strides; }
    @Override
    public String toString() { return "Horse " + id + " "; }

}

public class HorseRace implements Runnable {

    private static final int FINISH_LINE = 25;
    private static List<Horse> horses = new ArrayList<>();
    private static ExecutorService exec = Executors.newCachedThreadPool();

    @Override
    public void run() {
        StringBuilder s = new StringBuilder();
        //打印赛道边界
        for(int i = 0; i < FINISH_LINE; i++) {
            s.append("=");
        }
        System.out.println(s);
        //打印赛马轨迹
        for(Horse horse : horses) {
            System.out.println(horse.tracks());
        }
        //判断是否结束
        for(Horse horse : horses) {
            if(horse.getStrides() >= FINISH_LINE) {
                System.out.println(horse + "won!");
                exec.shutdownNow();
                return;
            }
        }
        //休息指定时间再到下一轮
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch(InterruptedException e) {
            System.out.println("barrier-action sleep interrupted");
        }
    }

    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(7, new HorseRace());
        for(int i = 0; i < 7; i++) {
            Horse horse = new Horse(barrier);
            horses.add(horse);
            exec.execute(horse);
        }
    }
}

实现多次使用源码:
nextGeneration();1.判断最后一个线程等待完成, 唤醒所有阻塞的线程,2.重置下count(count 每来一个线程都会进行减1)和generation,以便于下次循环。

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
         try {
               //获取barrier当前的 “代”也就是当前循环
             final Generation g = generation;
            if (g.broken)
                throw new BrokenBarrierException();
 
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            // 每来一个线程调用await方法都会进行减1
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    // new CyclicBarrier 传入 的barrierCommand, command.run()这个方法是同步的,如果耗时比较多的话,是否执行的时候需要考虑下是否异步来执行。
                    if (command != null)
                        command.run();
                    ranAction = true;
                    // 这个方法1. 唤醒所有阻塞的线程,2. 重置下count(count 每来一个线程都会进行减1)和generation,以便于下次循环。
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
 
            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                     // 进入if条件,说明是不带超时的await
                    if (!timed)
                         // 当前线程会释放掉lock,然后进入到trip条件队列的尾部,然后挂起自己,等待被唤醒。
                        trip.await();
                    else if (nanos > 0L)
                         //说明当前线程调用await方法时 是指定了 超时时间的!
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                     //Node节点在 条件队列内 时 收到中断信号时 会抛出中断异常!
                    //g == generation 成立,说明当前代并没有变化。
                    //! g.broken 当前代如果没有被打破,那么当前线程就去打破,并且抛出异常..
                    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.
                    //执行到else有几种情况?
                    //1.代发生了变化,这个时候就不需要抛出中断异常了,因为 代已经更新了,这里唤醒后就走正常逻辑了..只不过设置下 中断标记。
                    //2.代没有发生变化,但是代被打破了,此时也不用返回中断异常,执行到下面的时候会抛出  brokenBarrier异常。也记录下中断标记位。
                        Thread.currentThread().interrupt();
                    }
                }
               //唤醒后,执行到这里,有几种情况?
              //1.正常情况,当前barrier开启了新的一代(trip.signalAll())
              //2.当前Generation被打破,此时也会唤醒所有在trip上挂起的线程
              //3.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
                if (g.broken)
                    throw new BrokenBarrierException();
               //唤醒后,执行到这里,有几种情况?
            //1.正常情况,当前barrier开启了新的一代(trip.signalAll())
            //2.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
                if (g != generation)
                    return index;
               //唤醒后,执行到这里,有几种情况?
            //.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
             lock.unlock();
        }
    }

Semaphore
Semaphore 是一个计数信号量,必须由获取它的线程释放。
常用于限制可以访问某些资源的线程数量。
//创建具有给定的许可数和非公平的公平设置的 Semaphore。
Semaphore(int permits)
//创建具有给定的许可数和给定的公平设置的 Semaphore。
Semaphore(int permits, boolean fair)
使用最基本的acquire方法和release方法就可以实现Semaphore最常见的功能。

public class StudySemaphore {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();

        //信号量,只允许 3个线程同时访问
        Semaphore semaphore = new Semaphore(3);

        for (int i=0;i<10;i++){
            final long num = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取许可
                        semaphore.acquire();
                        //执行
                        System.out.println("Accessing: " + num);
                        Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
                        //释放
                        semaphore.release();
                        System.out.println("Release..." + num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        executorService.shutdown();
    }
}
posted @ 2021-06-18 21:22  倔强的老铁  阅读(72)  评论(0编辑  收藏  举报