CountDownLatch、CyclicBarrier和Semaphore的学习

CyclicBarrier

该类是一个同步的辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程(就是线程数的确定)的程序中,这些线程必须不时地互相等待。

我们设置这个平衡点的作用,就是保持所有线程同时到达。对于失败的同步尝试,CyclicBarrier 使用了一种要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其他所有线程也将通过 BrokenBarrierException(如果它们几乎同时被中断,则用 InterruptedException)以反常的方式离开。

简单示例
private static final int threadnumber = 4;
        public static void main(String[] args) {
             CyclicBarrier cb = new CyclicBarrier(threadnumber);
             //创建一个新的 CyclicBarrier,它将在给定数量的参与者
             //(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
             for(int i = 0;i<4;i++) {
                 Thread t = new Thread(new Test105(). new Mythread(cb));
                 t.setName("线程"+i);
                 t.start();
             }

        }
        class Mythread implements Runnable{
            CyclicBarrier bc;
            public Mythread(CyclicBarrier bc) {
                super();
                this.bc = bc;
            }

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("我是"+Thread.currentThread().getName()+"开始工作啦");
                try {
                    Thread.sleep(5000);//在这里模拟业务
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("我是"+Thread.currentThread().getName()+"工作结束啦");
                try {
                 bc.await();//当之前的线程如果不是所有的读执行完成,那么已执行完毕的会在这等待。全部执行结束才会执行下面操作。
                 //   在所有参与者都已经在此 barrier上,调用 await 方法之前,将一直等待。
                } catch (InterruptedException | BrokenBarrierException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("啦啦啦,所有线程都操作好啦");
            }

        }

运行结果

这里写图片描述

await方法

每次调用 await() 都将返回能到达屏障处的线程的索引。然后,您可以选择哪个线程应该执行屏障操作

该方法的返回值:- 1 指示将到达的第一个线程,零指示最后一个到达的线程

 int index = bc.await();
     if(index==0) {
                     System.out.println("我是最后达到的"+Thread.currentThread().getName()+"index:"+index);
                 }

我们还可以通过该类的另一个构造函数来实现。

这里写图片描述

基于上面的代码,我们做下面修改即可。

     CyclicBarrier cb = new CyclicBarrier(threadnumber,new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("我是"+Thread.currentThread().getName()+"最后到达,做的屏障操作");

                }
            });
await指定超时时间

在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。如果指定时间到达,没有到达,那么会抛出异常,但是还是会继续下面的任务,不会终止程序。

为了模拟线程执行慢,我们让线程睡一会。

private static final int threadnumber = 4;
    public static void main(String[] args) {
         CyclicBarrier cb = new CyclicBarrier(threadnumber,new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("我是"+Thread.currentThread().getName()+"最后到达,做的屏障操作");

            }
        });
         //创建一个新的 CyclicBarrier,它将在给定数量的参与者
         //(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
         for(int i = 0;i<4;i++) {
             if(i==3) {
                 try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
             }
             Thread t = new Thread(new Test106(). new Mythread(cb));
             t.setName("线程"+i);
             t.start();
         }

    }
    class Mythread implements Runnable{
        CyclicBarrier bc;
        public Mythread(CyclicBarrier bc) {
            super();
            this.bc = bc;
        }

        @Override
        public void run() {
            // TODO Auto-generated method stub
            System.out.println("我是"+Thread.currentThread().getName()+"开始工作啦");
            try {
                Thread.sleep(5000);//在这里模拟业务
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("我是"+Thread.currentThread().getName()+"工作结束啦");
            try {
                bc.await(2000,TimeUnit.MILLISECONDS);//在这里指定了时间,如果超出时间,
                //没有到达barrier,会报超时异常,并且程序会执行完线程体的内容
             //TimeUnit是个枚举类,TimeUnit 不维护时间信息,
            //但是有助于组织和使用可能跨各种上下文单独维护的时间表示形式。
            //毫微秒定义为千分之一微秒,微秒为千分之一毫秒,毫秒为千分之一秒,
            //一分钟为六十秒,一小时为六十分钟,一天为二十四小时。 
            /*  DAYS
                HOURS
                MICROSECONDS 微秒
                MILLISECONDS 毫秒
                MINUTES
                NANOSECONDS  毫微
                SECONDS*/

            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (TimeoutException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                System.out.println("超时了");
            }
            System.out.println("啦啦啦,所有线程都操作好啦");
        }

    }

效果图:

这里写图片描述

CyclicBarrier的重用

因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier,但是这个有个前提,就是你之前几个线程使用的barrier,必须全部释放后(所有到达后),这是barrier在可以新的一次使用。

代码很简单,和单次使用barrier差不多,这里就不放了

CountDownLatch

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

重要方法

这里写图片描述

await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier,CyclicBarrier是不可以重用的。

await后面指定时间的话,那么超过时间,是不会抛出异常,还是一样的执行后面的任务。

下面是我测试的效果图:

这里写图片描述

这样根本达不到我们使用的要求,那么你这样使用有啥子意思。

简单使用
  CountDownLatch cdl =  new CountDownLatch(2);//指加计数的线程数有2个
   for(int i = 0;i<2;i++) {
       if(i==1) {
           try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       }
       Thread m = new Thread(new Test108().new MyThread(cdl));
       m.setName("线程"+i);
       m.start();

   }

    }
  class MyThread implements Runnable{
      CountDownLatch sdl = null;


    public MyThread(CountDownLatch sdl) {
        super();
        this.sdl = sdl;
    }


    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"开始工作");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"工作完毕");
        sdl.countDown();//执行任务好了之后,计数器就减1
        try {
            sdl.await();//如果计数器中的不为1,那么会阻塞在这边。
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
      System.out.println("所有的都执行好了");

    }
Semaphore

一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。

将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。

此类的构造方法可选地接受一个公平参数。当设置为 false 时,此类不对线程获取许可的顺序做任何保证。特别地,闯入 是允许的,也就是说可以在已经等待的线程前为调用 acquire() 的线程分配一个许可,从逻辑上说,就是新线程将自己置于等待线程队列的头部。当公平设置为 true 时,信号量保证对于任何调用获取方法的线程而言,都按照处理它们调用这些方法的顺序(即先进先出;FIFO)来选择线程、获得许可。注意,FIFO 排序必然应用到这些方法内的指定内部执行点。所以,可能某个线程先于另一个线程调用了 acquire,但是却在该线程之后到达排序点,并且从方法返回时也类似。还要注意,非同步的 tryAcquire 方法不使用公平设置,而是使用任意可用的许可。

常用方法

 acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。

 release()用来释放许可。注意,在释放许可之前,必须先获获得许可。

tryAcquire() 尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false。

简单例子

若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:

 private int wokernumber = 8;
    static Semaphore s = new Semaphore(5);

    public static void main(String[] args) {
        for(int i = 0;i<8;i++) {
            Thread t = new Thread(new Test109().new Mythread(s));
            t.setName("工人"+i);
            t.start();

        }
    }
    class Mythread implements Runnable{
        Semaphore s = null;

        public Mythread(Semaphore s) {
            super();
            this.s = s;
        }

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                s.acquire();
                System.out.println("我是"+Thread.currentThread().getName()+"开始使用机器");
                Thread.sleep(2000);
                s.release();
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }

            System.out.println("我是"+Thread.currentThread().getName()+"使用完毕机器");



        }

    }

效果图:

这里写图片描述

这个类是可以用来设置连接数的,比如数据库的最大连接数。它主要保证了规定量的线程对资源操作。

总结

终于完成了这个三个类的学习,心情还是蛮激动,这只是一个开头,对他们稍微有了点认识,脑袋里对他们有了个概念,但是要说真正用好,还是不容易,结合业务逻辑,期待以后的提高。

CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;如果没执行,这个线程将一直等待,如果设置超时。
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。但是锁只可以一个对象操作,而Semaphore可以是几个。

参考学习:https://www.cnblogs.com/dolphin0520/p/3920397.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值