多线程篇 之通信工具类(Semaphore,CountDownLatch,CyclicBarrier)

介绍下JUC的 通信工具类 Semaphore, CountDownLatch,CyclicBarrier 三剑客。

一、 Semaphore

Semaphore 信号量,用来控制同一时间,资源可被多少个线程访问。

先看构造


    //可传入一个 boolean 值,控制抢锁是否是公平的。
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

	//默认是非公平的
 	public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

ReentrantLock 也有公平和非公平,构造方式一样,后续发文将,以及 AQS

什么是公平和非公平锁呢?

非公平锁: 就是争抢票,谁抢到票,票就是谁的。

公平锁: 来了个正义大侠守着, 一个线程进来了,若是看到前面有人排队,就老老实实排在队伍后面。(线程会先去检查队列有没有等待的线程,如果有就进入等待队列排队)

案例:

/**
 *
 *@author Saiuna
 *@date 2020/7/9 10:40
 */
public class SemaphoreDemo2 {

    public static void main(String[] args) {
        int count = 10;
        //允许n 个线程同时执行, 公平锁 
        Semaphore s = new Semaphore(2, true);
        ExecutorService executorService =  Executors.newFixedThreadPool(10);

        for (int i = 0; i < count; i++) {
            executorService.execute(()->{
                try {
                    //获得许可
                    s.acquire();
                    System.out.println(Thread.currentThread().getName() + " running...");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //释放许可
                    s.release();
                }
            });
        }
        executorService.shutdown();
    }
}

输出:观测输出,是两两线程间隔打印的,也就是同时只放行了两个线程跑,改变Semaphore(2, true);的参数看输出结果, 三个则会三个人一起跑。

pool-1-thread-1 running...
pool-1-thread-2 running...
pool-1-thread-3 running...
pool-1-thread-4 running...
pool-1-thread-6 running...
pool-1-thread-5 running...
pool-1-thread-7 running...
pool-1-thread-8 running...
pool-1-thread-9 running...
pool-1-thread-10 running...

使用场景: 限制流量

二 、CountDownLatch

CountDownLatch 源码的官方介绍。

一个同步辅助器,允许一个或多个线程一直等待,直到一组在其他线程执行的操作全部完成。

这是一个一次性的现象 - 计数无法重置。 如果需要重置计数的版本,请考虑使用 CyclicBarrier

先来看看它的构造方法

 //传入一个 count 值,用于计数。
public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

常用方法

//递减锁存器的计数,释放所有等待的线程,如果计数到达零
//If the current count equals zero then nothing happens.
public void countDown() {
    sync.releaseShared(1);
}

//当一个线程调用await方法时,就会阻塞当前线程。每当有线程调用一次 countDown 方法时,计数就会减 1。
//当 count 的值等于 0 的时候,被阻塞的线程才会继续运行。
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

案例

玩游戏时都会等待一些资源的加载,等前置资源都加载好了方能开始游戏~

难道 这就是你跟妈咪说网络慢衣服还没加载出来 理由吗哈哈

在这里插入图片描述

/**
 *
 *@author Saiuna
 *@date 2020/7/9 9:24
 */
public class CountDownLatchDemo {

    // 定义前置任务线程
    static class PreTaskThread implements Runnable{

        private String task;
        private CountDownLatch countDownLatch;

        public PreTaskThread(String task, CountDownLatch countDownLatch) {
            this.task = task;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                Random random = new Random();
                Thread.sleep(random.nextInt(1000));
                System.out.println("\r\n" + task + " - 任务完成");
                countDownLatch.countDown();
                if (countDownLatch.getCount() != 0) {
                    System.out.println(String.format("剩下%d个前置任务", countDownLatch.getCount()));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
            }
        }
    }

    public static void main(String[] args) {
        // 假设有三个模块需要加载
        CountDownLatch countDownLatch = new CountDownLatch(3);
        new Thread(() -> {
            try {
                System.out.println("等待数据加载...");
                System.out.println(String.format("还有%d个前置任务", countDownLatch.getCount()));
                countDownLatch.await();

                System.out.println("数据加载完成,正式开始游戏!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 前置任务
        new Thread(new PreTaskThread("加载地图数据", countDownLatch)).start();
        new Thread(new PreTaskThread("加载人物模型", countDownLatch)).start();
        new Thread(new PreTaskThread("加载背景音乐", countDownLatch)).start();
    }
}

输出:

等待数据加载...
还有3个前置任务

加载背景音乐 - 任务完成
剩下2个前置任务

加载人物模型 - 任务完成
剩下1个前置任务

加载地图数据 - 任务完成
数据加载完成,正式开始游戏!

这边等到前置任务都完成了,顺利进入游戏。

三、 CyclicBarrier

CyclicBarrirer从名字上来理解是“循环的屏障”的意思。前面提到了CountDownLatch一旦计数值count被降为0后,就不能再重新设置了,它只能起一次“屏障”的作用。而CyclicBarrier拥有CountDownLatch的所有功能,还可以使用reset()方法重置屏障。

先看它 的构造方法

// 构造方法
public CyclicBarrier(int parties) {
    this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
    // 具体实现
}

案例,同上述的游戏案例:

/**
 *
 *@author Saiuna
 *@date 2020/7/9 18:02
 */
public class CyclicBarrierDemo {
    static class PreTaskThread implements Runnable {

        private String task;
        private CyclicBarrier cyclicBarrier;

        public PreTaskThread(String task, CyclicBarrier cyclicBarrier) {
            this.task = task;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            // 假设总共三个关卡
            for (int i = 1; i < 4; i++) {
                try {
                    Thread.sleep(new Random().nextInt(1000));
                    System.out.println(String.format("关卡%d的任务%s完成", i, task));
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                cyclicBarrier.reset(); // 重置屏障
            }
        }
    }

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
            System.out.println("本关卡所有前置任务完成,开始游戏...");
        });

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(new PreTaskThread("加载地图数据", cyclicBarrier));
        executorService.execute(new PreTaskThread("加载地图数据", cyclicBarrier));
        executorService.execute(new PreTaskThread("加载背景音乐", cyclicBarrier));
    }
}

输出

关卡1的任务加载地图数据完成
关卡1的任务加载背景音乐完成
关卡1的任务加载人物模型完成
本关卡所有前置任务完成,开始游戏...
关卡2的任务加载地图数据完成
关卡2的任务加载背景音乐完成
关卡2的任务加载人物模型完成
本关卡所有前置任务完成,开始游戏...
关卡3的任务加载人物模型完成
关卡3的任务加载地图数据完成
关卡3的任务加载背景音乐完成
本关卡所有前置任务完成,开始游戏...

这里跟CountDownLatch的代码有一些不同。CyclicBarrier没有分为await()和countDown(),而是只有单独的一个await()方法。

一旦调用await()方法的线程数量等于构造方法中传入的任务总量(这里是3),就代表达到屏障了。CyclicBarrier允许我们在达到屏障的时候可以执行一个任务,可以在构造方法传入一个Runnable类型的对象。上述案例就是在达到屏障时,输出“本关卡所有前置任务完成,开始游戏…”。

总结

  1. CountDownLatch是一个计数器,线程完成一个记录一个,计数器递减 直到 0,只能只用一次.
  2. CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用
  3. CyclicBarrier 可以在最后一个线程达到屏障之前,选择先执行一个操作。
  4. Semaphore ,需要拿到许可才能执行,并可以选择公平和非公平模式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值