Java并发之CountDownLatch、CyclicBarrier、Semaphore使用实例

泪湿孤枕 提交于 2020-01-19 02:18:10

最近学习了Java并发编程中的CountDownLatch、CyclicBarrier、Semaphore,做个小总结。

这三个类都是java1.5中提供的一些非常有用的辅助类,用于帮助程序员实现并发编程。

CountDownLatch

  1. 背景
    1. CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。
    2. CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。
    3. countDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。
    4. 存在于java.util.cucurrent包下。
  2. 概念
    1. countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
    2. 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
  3. 源码
    1. countDownLatch类中只提供了一个构造器
      //参数count为计数值
      public CountDownLatch(int count) {  };  

       

    2. 类中有三个方法最重要
      //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
      public void await() throws InterruptedException { };   
      //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
      public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
      //将count值减1
      public void countDown() { }; 

       

  4. 实例
    import java.util.concurrent.CountDownLatch;
    
    public class CountDownLatchDemo {
    
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch countDownLatch = new CountDownLatch(6);
    
            for (int i = 1; i <= 6; i++) {
                new Thread(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t国,被灭");
                    countDownLatch.countDown();
                }, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
            }
    
            countDownLatch.await();
            System.out.println(Thread.currentThread().getName() + "\t 六国灭");
            
            //System.out.println(CountryEnum.SIX);
            //System.out.println(CountryEnum.SIX.getRetCode());
            //System.out.println(CountryEnum.SIX.getRetMessage());
    
        }
    }

     

  • 运行结果:
    魏	国,被灭
    齐	国,被灭
    赵	国,被灭
    韩	国,被灭
    楚	国,被灭
    燕	国,被灭
    main	 六国灭

    CyclicBarrier

  1. 概念
    1.   允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

      CyclicBarrier支持一个可选的Runnable命令,每个屏障点运行一次,在派对中的最后一个线程到达之后,但在任何线程释放之前。 在任何一方继续进行之前,此屏障操作对更新共享状态很有用。

        实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用await方法时,将拦截的线程数减1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁。

  2. 方法
    1. 构造方法
      1. //创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待它时,它将跳闸,并且当屏障跳闸时不执行预定义的动作。
        CyclicBarrier(int parties)
        //创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。
        CyclicBarrier(int parties, Runnable barrierAction)

         

    2. 常用方法
      1. //等待所有 parties已经在这个障碍上调用了 await 。
        int await() 
        //等待所有 parties已经在此屏障上调用 await ,或指定的等待时间过去。
        int await(long timeout, TimeUnit unit) 
        //返回目前正在等待障碍的各方的数量。
        int getNumberWaiting() 
        //返回旅行这个障碍所需的parties数量。
        int getParties() 
        //查询这个障碍是否处于破碎状态。
        boolean	isBroken()
        //将屏障重置为初始状态。
        void reset() 

         

  3. 示例
    /**
     * @program: java8_demo
     * @description: CyclicBarrier的使用
     * @author: XZQ
     * @create: 2020-01-06 10:43
     **/
    public class CyclicBarrierDemo {
    
        public static void main(String[] args) {
            CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("召唤神龙"));
    
            for (int i = 1; i <= 7; i++) {
                final int tempInt = i;
                new Thread(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 收集第:" + tempInt + "颗龙珠");
                    try {
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }, String.valueOf(i)).start();
            }
        }
    }

     

  4. 运行结果
    3	 收集第:3颗龙珠
    2	 收集第:2颗龙珠
    6	 收集第:6颗龙珠
    4	 收集第:4颗龙珠
    1	 收集第:1颗龙珠
    7	 收集第:7颗龙珠
    5	 收集第:5颗龙珠
    召唤神龙

    CountDownLatch和CyclicBarrier区别

 

  1. CountDownLatch和CyclicBarrier都是java.util.concurrent包下面的多线程工具类。

  2. CountDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次

  3. CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用

  4. 从字面上理解,CountDown表示减法计数,Latch表示门闩的意思,计数为0的时候就可以打开门闩了。Cyclic Barrier表示循环的障碍物。两个类都含有这一个意思:对应的线程都完成工作之后再进行下一步动作,也就是大家都准备好之后再进行下一步。然而两者最大的区别是,进行下一步动作的动作实施者是不一样的。这里的“动作实施者”有两种,一种是主线程(即执行main函数),另一种是执行任务的其他线程,后面叫这种线程为“其他线程”,区分于主线程。对于CountDownLatch,当计数为0的时候,下一步的动作实施者是main函数;对于CyclicBarrier,下一步动作实施者是“其他线程”。

Semaphore

  1. 概念 

    Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式。它相当于给线程规定一个量从而控制允许活动的线程数。

    简单来说 Semaphore 是一个计数信号量,必须由获取它的线程释放。

    常用于限制可以访问某些资源的线程数量,例如通过 Semaphore 限流。

  2. 方法
    1. 构造方法
      //构造方法,创建具有给定许可数的计数信号量并设置为非公平信号量。
      Semaphore(int permits)
      
      //构造方法,当fair等于true时,创建具有给定许可数的计数信号量并设置为公平信号量。
      Semaphore(int permits,boolean fair)

       

    2. 常用方法
      //从此信号量获取一个许可前线程将一直阻塞。相当于一辆车占了一个车位
      void acquire()
      
      //从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞。比如n=2,就相当于一辆车占了两个车位
      void acquire(int n)
      
      //释放一个许可,将其返回给信号量。就如同车开走返回一个车位
      void release()
      
      //释放n个许可
      void release(int n)
      
      //当前可用的许可数
      int availablePermits()

       

  3. 示例
    /**
     * @program: java8_demo
     * @description: Semaphore使用
     * @author: XZQ
     * @create: 2020-01-06 11:08
     **/
    public class SemaphoreDemo {
    
        public static void main(String[] args) {
            Semaphore semaphore = new Semaphore(3);//模拟3个车位
    
            for (int i = 1; i <= 6; i++) {
                new Thread(() -> {
                    try {
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "\t抢到车位");
                        TimeUnit.SECONDS.sleep(3);
                        System.out.println(Thread.currentThread().getName() + "\t停车三秒后离开车位");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        semaphore.release();
                    }
                }, String.valueOf(i)).start();
            }
        }
    }

     

  4. 运行结果
    4	抢到车位
    2	抢到车位
    3	抢到车位
    4	停车三秒后离开车位
    3	停车三秒后离开车位
    2	停车三秒后离开车位
    6	抢到车位
    5	抢到车位
    1	抢到车位
    5	停车三秒后离开车位
    1	停车三秒后离开车位
    6	停车三秒后离开车位
    
    如果将
    Semaphore semaphore = new Semaphore(3);//模拟3个车位改成
    Semaphore semaphore = new Semaphore(1);//模拟1个车位 就如同一个单例模式,即单个停车位,只有一辆车进,然后这辆车出来后,下一辆车才能进。
    运行结果就成为
    
    2	抢到车位
    2	停车三秒后离开车位
    3	抢到车位
    3	停车三秒后离开车位
    5	抢到车位
    5	停车三秒后离开车位
    6	抢到车位
    6	停车三秒后离开车位
    4	抢到车位
    4	停车三秒后离开车位
    1	抢到车位
    
    若改为6
    
    3	抢到车位
    4	抢到车位
    5	抢到车位
    1	抢到车位
    6	抢到车位
    2	抢到车位
    3	停车三秒后离开车位
    4	停车三秒后离开车位
    1	停车三秒后离开车位
    6	停车三秒后离开车位
    2	停车三秒后离开车位
    5	停车三秒后离开车位

     

  5. 总结
    1. Semaphore主要用于控制当前活动线程数目,就如同停车场系统一般,而Semaphore则相当于看守的人,用于控制总共允许停车的停车位的个数,而对于每辆车来说就如同一个线程,线程需要通过acquire()方法获取许可,而release()释放许可。如果许可数达到最大活动数,那么调用acquire()之后,便进入等待队列,等待已获得许可的线程释放许可,从而使得多线程能够合理的运行。

 枚举的使用

/**
 * @program: java8_demo
 * @description: 枚举的使用
 * @author: XZQ
 * @create: 2020-01-06 10:03
 **/
public enum CountryEnum {
    ONE(1, "齐"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "赵"), FIVE(5, "魏"), SIX(6, "韩");

    private Integer retCode;
    private String retMessage;

    public Integer getRetCode() {
        return retCode;
    }

    public String getRetMessage() {
        return retMessage;
    }

    CountryEnum(Integer retCode, String retMessage) {
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    public static CountryEnum forEach_CountryEnum(int index) {
        CountryEnum[] countryEnums = CountryEnum.values();
        for (CountryEnum element : countryEnums) {
            if (index == element.getRetCode())
                return element;
        }
        return null;
    }
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!