java并发编程2.2并发工具类——CountDownLatch和CyclicBarrier使用及比较

环境:

jdk1.8

摘要说明:

上一章节主要讲述如何按照fork-join范式如何将大任务划分成多个小任务分而治之;

从本章节主要讲述两个工具类CountDownLatch和CyclicBarrier使用及比较;

CountDownLatch:一种同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成;CountDownLatch使用给定的计数初始化。wait方法阻塞,直到当前计数由于countDown()方法的调用而达到零,在此之后释放所有等待的线程,并立即返回任何后续的wait调用。这是一个一次性现象——计数无法重置。

CyclicBarrier:一种同步辅助工具,它允许一组线程相互等待到达共同的屏障点。循环屏障在涉及固定大小的线程组的程序中非常有用,这些线程组偶尔必须相互等待。这个屏障被称为循环的,因为它可以在等待的线程释放后被重用。

步骤:

1.CountDownLatch的基础用法

CountDownLatch:一种同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成;

CountDownLatch使用给定的计数初始化。wait方法阻塞,直到当前计数由于countDown()方法的调用而达到零,在此之后释放所有等待的线程,并立即返回任何后续的wait调用。这是一个一次性现象——计数无法重置。如果您需要一个可以重置计数的版本,可以考虑使用cycle barrier。

CountDownLatch是一种通用的同步工具,可以用于多种目的。其中经典的两种使用方式有:

  • 方式一:用一个count初始化的CountDownLatch用作简单的开/关锁或门:所有想打开该门的线程都在等待,直到CountDownLatch被调用countDown()方法count次为零,所有线程释放进入门做各自业务;
  • 方式二:初始化为N的CountDownLatch可以让一个线程等待,直到N个线程完成某个操作,或者某个操作已经完成N次。

方式一实践:

由于这种方式很贴近运动员赛跑,需等待所有运动员准备完成之后才统一起跑,所以这种方式CountDownLatch也被称作发令枪;这个调用方式也常用于模拟高并发,大家可以参考之前的文章高并发编程之高并发场景:秒杀(无锁、排他锁、乐观锁、redis缓存的逐步演变)

package pers.cc.curriculum2.countDownLatch;

import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch测试类:10个运动员一一准备,等都准备好统一听发令枪一起起跑
 * 
 * 
 * @author cc
 *
 */
public class CountDownLatchAwaitN {
    // 定义一个总数为14的计数器
    static CountDownLatch latch = new CountDownLatch(14);

    private static class InitThread implements Runnable {
        private String name;

        public InitThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println(name + "已准备好!");
            // 初始化线程完成工作了,调用countDown方法扣减一次;
            latch.countDown();
            try {
                // 堵塞所有子进程等候计数器号令
                latch.await();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "启动:" + System.currentTimeMillis());

        }

    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 10; i++) {
            Thread thread = new Thread(new InitThread("运动员" + i));
            thread.start();
        }
        Thread.sleep(1000);
        System.out.println("3");
        latch.countDown();
        System.out.println("2");
        latch.countDown();
        System.out.println("1");
        latch.countDown();
        System.out.println("啪");
        latch.countDown();
    }
}

运行结果如下,可以看到子线程被await方法堵塞,知道计算器减到0统一启动:

运动员1已准备好!
运动员2已准备好!
运动员6已准备好!
运动员10已准备好!
运动员3已准备好!
运动员4已准备好!
运动员7已准备好!
运动员5已准备好!
运动员8已准备好!
运动员9已准备好!
3
2
1
啪
运动员1启动:1549087623847
运动员6启动:1549087623847
运动员4启动:1549087623847
运动员2启动:1549087623847
运动员3启动:1549087623847
运动员9启动:1549087623847
运动员10启动:1549087623847
运动员8启动:1549087623847
运动员5启动:1549087623847
运动员7启动:1549087623847

方式二实践:

方式二就类似我们需要装货出发一样;n个人同时搬货,只有当我们车上装满了指定个数count个货物,才可以进行配送;

package pers.cc.curriculum2.countDownLatch;

import java.util.concurrent.CountDownLatch;

import pers.cc.tools.SleepTools;

/**
 * CountDownLatch测试类:装满指定个数货物才能进行配送
 * 
 * 
 * @author cc
 *
 */
public class CountDownLatchAwaitOne {
    // 定义一个总数为30的计数器
    static CountDownLatch latch = new CountDownLatch(30);

    private static class InitThread implements Runnable {

        @Override
        public void run() {
            // 每个人装3个货物
            for (int i = 1; i <= 3; i++) {
                SleepTools.ms(200);
                System.out.println("Thread_" + Thread.currentThread().getId()
                        + "已准备好一个货物!");
                // 初始化线程完成工作了,调用countDown方法扣减一次;
                latch.countDown();
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        // 10个人装,每个人装3个货物
        for (int i = 1; i <= 10; i++) {
            Thread thread = new Thread(new InitThread());
            thread.start();
        }
        latch.await();
        System.out.println("货物装好出发");
    }
}

上述的演示结果如下:

Thread_15已准备好一个货物!
.......
Thread_19已准备好一个货物!
货物装好出发

总结

构造方法

CountDownLatch(int count):构造使用给定的计数初始化的CountDownLatch。

常用方法

  • void  await():使当前线程等待,直到计数器计数到零,除非线程被中断。
  • boolean  await(long timeout, TimeUnit unit) :使当前线程等待,直到锁存器计数到零,除非线程被中断,或指定的等待时间已经过。
  • void   countDown():减少计数器的计数,如果计数为零,释放所有等待的线程。
  • long   getCount():返回当前计数

2.CyclicBarrier的基础用法

CyclicBarrier:一种同步辅助工具,它允许一组线程相互等待到达共同的屏障点。循环屏障在涉及固定大小的线程组的程序中非常有用,这些线程组偶尔必须相互等待。这个屏障被称为循环的,因为它可以在等待的线程释放后被重用。

cycles barrier支持一个可选的Runnable命令,该命令在参与方的最后一个线程到达之后,但在任何线程被释放之前,在每个屏障点上运行一次。此屏障操作对于在任何缔约方继续之前更新共享状态非常有用。

cycles barrier适用的场景:

场景一:就是指定线程都进入指定等待状态后;统一进行后续的动作;

场景二:就是指定线程都进入指定等待状态后;先进行指定Runnable的操作后,再统一进行后续的动作;

package pers.cc.curriculum2.cyclesBarrier;

import java.util.Map;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;

import pers.cc.tools.SleepTools;

/**
 * CyclesBarrier:一组线程相互等待到达共同的屏障点,可选择先执行一个Runnable操作,也可立即进行线程的后续操作
 * 
 * @author cc
 *
 */
public class CyclesBarrierTest {
    // 场景1:定义一组线程相互等待后立即执行后续
    private static CyclicBarrier barrier1 = new CyclicBarrier(5);

    // 场景2:定义一组线程相互等待后先执行CollectThread线程再执行各自后续
    private static CyclicBarrier barrier2 = new CyclicBarrier(5,
            new CollectThread());

    // 存放子线程工作结果的容器
    private static ConcurrentHashMap < String, Long > resultMap = new ConcurrentHashMap <>();

    /**
     * 场景1初始线程
     * 
     * @author cc
     *
     */
    private static class InitThread1 implements Runnable {
        @Override
        public void run() {
            long id = Thread.currentThread().getId();
            try {
                Thread.sleep(200 + id);
                System.out.println(id + "....is await");
                barrier1.await();
                System.out.println("Thread_" + id + " ....do its business ");
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            catch (BrokenBarrierException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    /**
     * 场景2初始线程
     * 
     * @author cc
     *
     */
    private static class InitThread2 implements Runnable {

        @Override
        public void run() {
            long id = Thread.currentThread().getId();
            // 线程本身的处理结果
            resultMap.put(Thread.currentThread().getId() + "", id);
            // 随机决定工作线程的是否睡眠
            Random r = new Random();
            try {
                if (r.nextBoolean()) {
                    Thread.sleep(2000 + id);
                }
                System.out.println(id + "....is await");
                barrier2.await();
                System.out.println("Thread_" + id + " ....do its business ");
            }
            catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    // 负责屏障开放以后的工作
    private static class CollectThread implements Runnable {

        @Override
        public void run() {
            StringBuilder result = new StringBuilder();
            for (Map.Entry < String, Long > workResult : resultMap.entrySet()) {
                result.append("[" + workResult.getValue() + "]");
            }
            System.out.println(" the result = " + result);
            System.out.println("do other business........");
        }
    }

    public static void main(String[] args) {
        System.out
                .println("——————————————————————场景1——————————————————————————");
        for (int i = 0; i < 5; i++) {
            new Thread(new InitThread1()).start();
        }
        // 休眠两秒
        SleepTools.second(2);
        System.out
                .println("——————————————————————场景2——————————————————————————");
        for (int i = 0; i < 5; i++) {
            new Thread(new InitThread2()).start();
        }
    }
}

运行结果如下:

——————————————————————场景1——————————————————————————
10....is await
11....is await
12....is await
13....is await
14....is await
Thread_10 ....do its business 
Thread_11 ....do its business 
Thread_12 ....do its business 
Thread_13 ....do its business 
Thread_14 ....do its business 
——————————————————————场景2——————————————————————————
16....is await
17....is await
15....is await
18....is await
19....is await
 the result = [15][16][17][18][19]
do other business........
Thread_19 ....do its business 
Thread_16 ....do its business 
Thread_18 ....do its business 
Thread_15 ....do its business 
Thread_17 ....do its business 

总结

构造方法

CyclicBarrier(int parties):创建一个新的cycle barrier,当给定数量的参与方(线程)等待它时,它将跳闸即消除屏障,当跳闸时,它不会执行预定义的操作。

CyclicBarrier(int parties, Runnable barrierAction):创建一个新的cycle barrier,当给定数量的参与方(线程)等待它时,它将跳闸即消除屏障,当跳闸时,它会进入执行的barrierAction线程先执行预定操作;

常用方法

  • void  await():使当前线程等待,直到计数器计数到零,除非线程被中断。
  • boolean  await(long timeout, TimeUnit unit) :使当前线程等待,直到锁存器计数到零,除非线程被中断,或指定的等待时间已经过。
  • void   getNumberWaiting():返回当前在屏障处等待的参与方的数量。
  • long   getParties():返回访问此屏障所需的参与方的数量。
  • boolean   isBroken():查询此屏障是否处于破坏状态。
  • void   reset():将屏障重置为初始状态。

3.CountDownLatch和CyclicBarrie的比较

复用性上来说,CountDownLatch是一次性的,CyclicBarrie是可复用的;

阻塞线程数量上来说:CyclicBarrie必须阻塞所有子线程;CountDownLatch更加自由,可只阻塞一个主线程,也可阻塞所有子线程;

根本思路上说:CountDownLatch只是一个计数器,当计数器计数为0时,受他堵塞的线程得到释放;而CyclicBarrie是一组线程间的相互等待,它的堵塞想要得到释放必须要所有参于线程都到达指定屏障;

4.源码地址

https://github.com/cc6688211/concurrent-study.git

你可能感兴趣的:(高并发编程,java并发编程)