多线程面试——CountDownLatch,CyclicBarrier,Semaphore

0.总结

1.CountDownLatch是1个线程等待其他线程,CyclicBarrier是多个线程相互等待;
2.CountDownLatch是计数-1,直到减为0,CyclicBarrier是计数+1,直到达到指定值;
3.CountDownLatch是一次性的,CyclicBarrier是循环使用的;
4.CyclicBarrier可以在最后一个线程到达后,选择1执行1个优先操作;
5.Semaphore,需要拿到锁才能执行,并且可以选择公平非公平的模式

1.CountDownLatch——基于AQS实现

它是一个同步辅助器,允许一个或多个线程一直等待,直到一组在其他线程执行的操作全部完成。
在这里插入图片描述

构造方法

会传入一个 count 值,用于计数。

public CountDownLatch(int count) {
	if (count < 0) throw new IllegalArgumentException("count < 0");
	this.sync = new Sync(count);
}
常用的方法有2个:await、countDown
  • 1.await方法
    当一个线程调用await方法时,就会阻塞当前线程。
public void await() throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}
  • 2.countDown 方法
    每当有线程调用一次 countDown 方法时,计数就会减 1。当 count 的值等于 0 的时候,被阻塞的线程才会继续运行。
public void countDown() {
	sync.releaseShared(1);
}
场景设计

CountDownLatch阻塞的是主线程。

  • 主线程:领导
  • 分支线程:多个worker

现在设想一个场景,公司项目,线上出现了一个紧急 bug,被客户投诉,领导焦急的过来,想找人迅速的解决这个 bug 。

那么,一个人解决肯定速度慢啊,于是领导(主线程)叫来张三和李四,一起分工解决。终于,当他们两个都做完了自己所需要做的任务之后,领导(主线程)才可以答复客户,客户也就消气了。

于是,我们可以设计一个 Worker 类(分支线程类)来模拟单个人修复 bug 的过程,领导(主线程)一直等待所有 Worker 任务执行结束,主线程才可以继续往下走。

  • bug如果1个人修复,需要耗时5秒,现在张三干2秒,李四干3秒,且并发进行,则只需要3秒即可完工。
    在这里插入图片描述
public class CountDownTest {
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        Worker w1 = new Worker("张三", 2000, latch);
        Worker w2 = new Worker("李四", 3000, latch);
        w1.start();
        w2.start();

        long startTime = System.currentTimeMillis();
        latch.await();
        System.out.println("bug全部解决,领导可以给客户交差了,任务总耗时:"+ (System.currentTimeMillis() - startTime));

    }

    static class Worker extends Thread{
        String name;
        int workTime;
        CountDownLatch latch;

        public Worker(String name, int workTime, CountDownLatch latch) {
            this.name = name;
            this.workTime = workTime;
            this.latch = latch;
        }

        @Override
        public void run() {
            System.out.println(name+"开始修复bug,当前时间:"+sdf.format(new Date()));
            doWork();
            System.out.println(name+"结束修复bug,当前时间:"+sdf.format(new Date()));
            latch.countDown();
        }

        private void doWork() {
            try {
                //模拟工作耗时
                Thread.sleep(workTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.CyclicBarrier—基于Reentrantlock实现

一组线程会互相等待,直到所有线程都到达一个同步点
* 就像一群人被困到了一个栅栏前面,只有等最后一个人到达之后,他们才可以合力把栅栏(屏障)突破。
在这里插入图片描述

1.两种构造方法

1.第1个参数parties(int型),指的是需要几个线程一起到达,才可以使所有线程取消等待。

public CyclicBarrier(int parties) {
    this(parties, null);
}

2.构造方法2多了第2个参数barrierAction(Runnable型),用于在所有线程达到屏障时,优先执行 barrierAction。

public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}
2.常用方法——await()
  • await();
    在下边的例子中,执行1次barrier.await(),计数器+1,等到计数器值达到CyclicBarrier设定值时(本例中是3),那大家一起冲破屏障
3.场景模拟
1.跑步比赛(忽略裁判)——大家都准备好了就一起跑
  • 使用构造方法1
class BarrierTest {
    public static void main(String[] args) {
    	1.参数:4,质需要4个线程到达才可以开跑
        CyclicBarrier barrier = new CyclicBarrier(4);  //①
        2.创建4个运动员:张三、李四、王五、赵六
        Runner runner1 = new Runner(barrier, "张三");
        Runner runner2 = new Runner(barrier, "李四");
        Runner runner3 = new Runner(barrier, "王五");
        Runner runner4 = new Runner(barrier, "赵六");
        
		3..创建线程池
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.execute(runner1);
        service.execute(runner2);
        service.execute(runner3);
        service.execute(runner4);

        service.shutdown();

    }

}
	2.运动员类
class Runner implements Runnable{

    private CyclicBarrier barrier;
    private String name;

    public Runner(CyclicBarrier barrier, String name) {
        this.barrier = barrier;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            1.模拟准备耗时
            Thread.sleep(new Random().nextInt(5000));
            System.out.println(name + ":准备OK");
            2.屏障等待
            barrier.await();
            System.out.println(name +": 开跑");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e){
            e.printStackTrace();
        }
    }
}
2.跑步比赛–准备好后,等待裁判吹哨子
  • 使用构造方法2——加入优先执行action(本例是:等待裁判吹哨子)
    所有运动员都准备好之后,再,等待裁判吹哨子。

  • 上边的代码修改如下:

1.创建等待裁判的动作
Judge waiteJudge = new Judge();
2.调用构造方法2
CyclicBarrier barrier = new CyclicBarrier(4,waiteJudge);

3.建立裁判Jugeclass Judge implements Runnable{
    @Override
    public void run() {
        try {
            System.out.println("等裁判吹口哨...");
            这里停顿两秒更便于观察线程执行的先后顺序
            Thread.sleep(2000);
            System.out.println("裁判吹口哨->>>>>");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4.CyclicBarrier的循环使用

张三、李四、王五、赵六,4个人分两批开跑。

  • 代码修改部分:CyclicBarrier 传入参数是2,线程池核心线程数也是2
    .

2个人开跑后(即释放2个核心线程后),另外2个人才开始准备。

CyclicBarrier barrier = new CyclicBarrier(2,waiteJudge);
ExecutorService service = Executors.newFixedThreadPool(2);

3.Semaphore

Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制。
在这里插入图片描述

1.构造方法(默认是非公平锁)

方法1,参数permits代表信号量个数;

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

方法2:参数boolean fair,为true,表示使用公平锁;false:表示非公平锁。

(公平锁的机制是通过AQS实现)
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

2.场景设计

  • 20辆车经过1座桥,每次只能同时有5辆车在桥上,超过5辆就会导致桥梁垮塌。
  • 基于Semaphore来设置5把锁,车拿到锁可以上桥,过完桥就归还锁,然后后边的车再取锁。
class SemaphoreTest {
	1.总共20辆车
    private static int count = 20;
    public static void main(String[] args) {
		2.建立核心线程数
        ExecutorService executorService = Executors.newFixedThreadPool(count);
        3.利用信号量:指定最多只能有4个线程同时执行
        Semaphore semaphore = new Semaphore(5);
        4.random 模拟每辆车的耗时时间
        Random random = new Random();
        5.依次提交20个线程到线程池
        for (int i = 0; i < count; i++) {
            final int no = i;
            Car car = new Car(semaphore,no,random);
            executorService.execute(car);
        }
        6.关闭线程池
        executorService.shutdown();
    }
}

1.新建汽车类
class Car implements Runnable{
    Semaphore semaphore;
    int no;
    Random random;

    public Car(Semaphore semaphore, int no, Random random) {
        this.semaphore = semaphore;
        this.no = no;
        this.random = random;
    }

    @Override
    public void run() {
        try {
            1.获得许可
            semaphore.acquire();
            System.out.println(no +":号车可通行");
            2.模拟车辆通行耗时
            Thread.sleep(random.nextInt(2000));
            3.释放许可
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值