1.倒计时协作器CountDownLatch

​ CountDownLatch可以用来实现一个(或者多个)线程等待其他线程完成一组特定的操作之后继续运行。

​ 场景:cf房间中有10个玩家,9个玩家必须全部准备才能开始游戏;

代码实现:

public class CountDownLatchTest {
    public static  final  CountDownLatch countDownLatch = new CountDownLatch(9);
    public static void main(String[] args) {
        new Thread(()->{
            try {
                countDownLatch.await();
                System.out.println("游戏开始,小心爆头");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        for(  int i =0;i<9;i++){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName()+"玩家准备完毕");
            }).start();
        }
    }
}

​ CountDownLatch内部会维护一个用户表示未完成的先决操作数量的计数器。CountDownLatch.countDown()方法执行一次,计数器的数量就会减少1。CountDownLatch.await()方法在计数器不为0的时候,当前线程会被暂停,当计数器达到0时,相应实例上等待的所有线程会别唤醒。

​ 当计数器值达到0时,计数器的值不会再发生便会。此时调用CountDownLatch.countDown()不会有异常抛出,CountDownLatch.await()的线程也不会被暂停。CountDownLatch只能使用一次。CountDownLatch.getCount()方法可以查询当前计数器的值。

2.栅栏CyclicBarrier

​ 场景:lol游戏,在选择完英雄之后,必须等待10名玩家游戏全部加载完成之后,一起进入游戏,

​ CyclicBarrier的作用就是等待所有线程全部达到一个点之后再一起执行。

代码实现:

 public static final   CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
    public static void main(String[] args) {
        while (true){
            for(  int i =0;i<10;i++){
                new Thread(()->{
                    try {
                        try {
                            //休眠一段时间,表示不同的加载时间
                            Random random = new Random();
                            Thread.sleep(random.nextInt(10000));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"玩家准备完毕");
                        //线程暂停,等待其他玩家加载成功,再一起进入游戏
                        cyclicBarrier.await();
                        System.out.println(Thread.currentThread().getName()+"进入游戏");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }

                }).start();
            }

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //getNumberWaiting 获取等待的数量
            while (cyclicBarrier.getNumberWaiting()!=0){
                //游戏没有结束耐心等待
            }

            System.out.println("游戏结束,等待游戏结果");
            System.out.println("下一把开始,请大家准备");

        }

    }

​ 调用CyclicBarrier.await()方法,当前线程被暂停,当最后一个线程调用CyclicBarrier.await()方法时,会使得使用当前实例的暂停的所有线程唤醒。与CountDownLatch的不同点是,当所有线程被唤醒之后,下一次调用await()方法又会暂停,又需要等待最后的线程都执行之后才能唤醒,是可以重复使用的。

​ 注意:CyclicBarrier经常用来模拟高并发的测试,如果一个线程需要等待另一个线程的话,在很多场景Thread.join()即可实现效果。

CyclicBarrier方法

//获取调用await()方法线程的数量
public int getNumberWaiting();
//获取屏障的数量
public int getParties();
//等待  返回值为当前线程的索引,0表示当前线程是最后一个到达的线程
public int await();
public int await(long timeout, TimeUnit unit);
//此屏障是否处于断开状态
public boolean isBroken();
//将屏障重置为初始状态
//调用初始化之后再await()等的线程会抛异常
public void reset()

3.限流Semaphore

​ 现在服务器有很多种限流的方式,Semaphore是jdk提供的一种限流方式。

@RestController
public class TestController {
    // 限流2 个
    public static final Semaphore semaphore = new Semaphore(2);
    @RequestMapping("/semaphore/test")
    public String test(){
        try {
            semaphore.acquire();
            System.out.println("1-获取资源");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
        return "success";
    }
    @RequestMapping("/semaphore/test2")
    public String test2(){
        try {
            semaphore.acquire();
            System.out.println("2-获取资源");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
        return "success";
    }
    @RequestMapping("/semaphore/test3")
    public String test3(){
        try {
            boolean b = semaphore.tryAcquire(1,1000, TimeUnit.MILLISECONDS);
            if(b){
                System.out.println("3-获取资源");
                semaphore.release();
            }else{
                System.out.println("3-获取资源失败");
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

按顺序请求这三个方法输出:

1-获取资源
2-获取资源
3-获取资源失败

当前限流2个,test和test2方法同时获取资源后等待10秒释放,test3方法试着获取资源(等待1秒)。等test和test2有一个调用release释放方法之后,test3就可以成功获取资源。

注意:

  • Semaphore.acquire/release方法要配对使用,使用acquire申请资源,使用release释放资源。Semaphore即使在没有申请到资源的情况下,还是可以通过release释放资源,这里就要自己通过代码进行合适的处理。和锁不同的是,锁必须获得锁才能释放锁,这里并没有这种限制。
  • Semaphore.release方法尽可能的放到finally中避免资源无法归还。
  • 如果Semaphore的配额为1,那么创建的实例相当与一个互斥锁,由于可以在没有申请资源的情况调用release释放资源,所以,这里允许一个线程释放另一个线程的锁。
  • Semaphore在申请资源的时候不是公平的,因此,如果当前配额为0 时,所有线程在申请的时候是等待状态,一旦有线程释放资源,会有等待的线程中任意一个申请成功。