CountDownLatch、CyclicBarrier、Semaphore基础使用

CountDownLatch、CyclicBarrier、Semaphore基础使用

jdk1.7引入的好玩意

Phaser相位器(移相器),可以重复使用的循环屏障。
https://blog.csdn.net/m0_37540696/article/details/113521523

CountDownLatch

CountDownLatch是一个同步工具类,从命名可以解读到 countdown 是倒数的意思,类似于我们倒计
时的概念。

CountDownLatch提供了两个方法,一个是 countDown,一个是 await, CountDownLatch初始化的时候需要传入一个整数,在这个整数倒数到 0 之前,调用了 await 方法的程序都必须要等待,然后通过 countDown 来倒数。

使用案例一

public static void main(String[] args) throws InterruptedException {
    CountDownLatch countDownLatch=new CountDownLatch(3);
    new Thread(()->{
        System.out.println(""+Thread.currentThread().getName()+"- 执行中");
        countDownLatch.countDown();
        System.out.println(""+Thread.currentThread().g
        etName()+"- 执行完毕");
    },"t1").start();
    new Thread(()->{
        System.out.println(""+Thread.currentThread().g
        etName()+"- 执行中");
        countDownLatch.countDown();
        System.out.println(""+Thread.currentThread().g
        etName()+"- 执行完毕");
    },"t2").start();
    new Thread(()->{
        System.out.println(""+Thread.currentThread().g
        etName()+"- 执行中");
        countDownLatch.countDown();
        System.out.println(""+Thread.currentThread().g
        etName()+"- 执行完毕");
    },"t3").start();
    countDownLatch.await();
    System.out.println(" 所有线程执行完毕");
}

从代码的实现来看,有点类似 join 的功能,但是比 join 更加灵活。CountDownLatch 构造函数会接收一个 int 类型的参数作为计数器的初始值,当调用 CountDownLatch 的countDown 方法时,这个计数器就会减一。通过 await 方法去阻塞去阻塞主流程。

高并发场景案例二

    static CountDownLatch countDownLatch=new CountDownLatch(1);
    @Override
    public void run() {
        try {
            countDownLatch.await();
            //TODO
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("ThreadName:"+Thread.currentThread().getName());
    }
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<1000;i++){
            new Demo().start();
        }
    	countDownLatch.countDown();
	}

凡是涉及到需要指定某个线程在执行之前,要等到前置线程执行完毕之后才执行的场景,都可以CountDownLatch。

对于 CountDownLatch,我们仅仅需要关心两个方法,一个是 countDown() 方法,另一个是 await() 方法。
countDown() 方法每次调用都会将 state 减 1,直到state 的值为 0;而 await 是一个阻塞方法,当 state 减
为 0 的时候,await 方法才会返回。await 可以被多个线程调用,大家在这个时候脑子里要有个图:所有调用了
await 方法的线程阻塞在 AQS 的阻塞队列中,等待条件满足(state == 0),将线程从队列中一个个唤醒过来。

Semaphore

semaphore 也就是我们常说的信号灯,semaphore 可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过 release 释放一个许可。有点类似限流的作用。叫信号灯的原因也和他的用处有关,比如某商场就 5 个停车位,每个停车位只能停一辆车,如果这个时候来了 10 辆车,必须要等前面有空的车位才能进入。

使用案例一

public class Test {
    public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(5);
            for(int i=0;i<10;i++){
            	new Car(i,semaphore).start();
            }
        }
        static class Car extends Thread{
            private int num;
            private Semaphore semaphore;
            public Car(int num, Semaphore semaphore) {
            	this.num = num;
            	this.semaphore = semaphore;
        	}
        public void run(){
            try {
                    semaphore.acquire();// 获取一个许可
                    System.out.println(" 第"+num+" 占用一个停车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(" 第"+num+" 俩车走喽");
                    semaphore.release();
                } catch (InterruptedException e) {
               		 e.printStackTrace();
            	}	
        }
    }
}

使用场景

Semaphore 比较常见的就是用来做限流操作了。

Semaphore 公平策略和非公平策略

FairSync

    /**
     * Fair version
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

        FairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            for (;;) {
                // 区别就在于是不是会先判断是否有线程在排队,然后才进行 CAS 减操作
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }	

NofairSync

通过对比发现公平和非公平的区别就在于是否多了一个hasQueuedPredecessors 的判断

    /**
     * NonFair version
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }
//
  final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

由于后面的代码和 CountDownLatch 的是完全一样,都是基于共享锁的实现。

CyclicBarrier

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续工作。CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 当前线程已经到达了屏障,然后当前线程被阻塞

使用场景

当存在需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用 CyclicBarrier。

使用案例一

DataImportThread

public class DataImportThread extends Thread{
private CyclicBarrier cyclicBarrier;
private String path;
public DataImportThread(CyclicBarrier cyclicBarrier, String path) {
    this.cyclicBarrier = cyclicBarrier;
    this.path = path;
}
@Override
public void run() {
    System.out.println(" 开始导入:"+path+" 位置的数据");
    try {
    	cyclicBarrier.await();//阻塞
    } catch
    (InterruptedException e) {
    	e.printStackTrace();
    } catch
    (BrokenBarrierException e) {
    	e.printStackTrace();
    }
}
}

CycliBarrierDemo

public class CycliBarrierDemo extends Thread{
    @Override
    public void run() {
        System.out.println("数据准备完毕,开始进行数据分析");
    }
    public static void main(String[] args) {
        CyclicBarrier cycliBarrier=new CyclicBarrier(3,new CycliBarrierDemo());
        new Thread(new DataImportThread(cycliBarrier,"file1")).start();
        new Thread(new DataImportThread(cycliBarrier,"file2")).start();
        new Thread(new DataImportThread(cycliBarrier,"file3")).start();
    }
}

注意点

1)对于指定计数值 parties,若由于某种原因,没有足够的线程调用 CyclicBarrier 的 await,则所有调用 await 的线程都会被阻塞;
2)同样的 CyclicBarrier 也可以调用 await(timeout, unit),设置超时时间,在设定时间内,如果没有足够线程到达,则解除阻塞状态,继续工作;
3)通过 reset() 重置计数(重置规则),会使得进入 await 的线程出现BrokenBarrierException,线程重新排队;

reset() 方法,重置 barrier 到初始化状态

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}

4 ) 如 果 采 用 是 CyclicBarrier(int parties, RunnablebarrierAction) 构造方法,执行 barrierAction 操作的是最后一个到达的线程。
5)CyclicBarrier 与 CountDownLatch 功能很像,只是 CyclicBarrier 可以有不止一个栅栏,因为它的栅栏(Barrier)的计数器可以重置,重复使用(Cyclic)。
6)内部有一个线程把规则破坏了(接收到中断信号)或者等待await(time)时间后,会调用#breakBarrier()通知其他线程,其他线程都不按规则来了,不会等待(await())了,则其他所有线程都将抛出 BrokenBarrierException 异常,并将 barrier 置于损坏状态,继续执行。

private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}

7)Generation 是 CyclicBarrier 内部静态类,描述了 CyclicBarrier 的更新换代。在CyclicBarrier中,同一批线程属于同一代。当有 parties 个线程全部到达 barrier 时,generation 就会被更新换代。其中 broken 属性,标识该当前 CyclicBarrier 是否已经处于中断状态。代码如下:

private static class Generation {
    boolean broken = false;
}

默认 barrier 是没有损坏的。
8)nextGeneration()
当所有线程都已经到达 barrier 处(index == 0),则会通过 nextGeneration() 方法,进行更新换代操作。在这个步骤中,做了三件事:

  1. 唤醒所有线程。
  2. 重置 count 。
  3. 重置 generation 。
private void nextGeneration() {
    trip.signalAll();
    count = parties;
    generation = new Generation();
}

实现原理

CyclicBarrier 相比 CountDownLatch 来说,要简单很多,源码实现是基于 ReentrantLock 和 Condition 的组合使用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值