Java并发辅助类

序言

由于最近项目上遇到了高并发问题,而自己对高并发,多线程这里的知识点相对薄弱,尤其是基础,所以想系统的学习一下,以后可能会出一系列的JUC文章及总结 ,同时也为企业级的高并发项目做好准备。

本文是JUC文章的第五篇,如想看以往关于JUC文章,请点击JUC系列总结

此系列文章的总结思路大致分为三部分:

  1. 理论(概念);
  2. 实践(代码证明);
  3. 总结(心得及适用场景)。

在这里提前说也是为了防止大家看着看着就迷路了。

备注:本文的知识深度相对较浅,可能仅局限于应用层面,如您需要相应足够的深度,请另行查阅。

Java并发辅助类大纲

CountDownLatch

Java辅助类.png

什么是CountDownLatch呢?

CountDownLatch:一个线程(或者多个线程),等待另外N个线程完成某个事情之后,方可执行。

可能大家看完之后还是云里雾里的,我还是给大家举一个实际场景的例子把。

模拟场景:

  1. 教室里有7个人上自习,分为为一个班长和其他6位同学;

  2. 现在的要求是班长必须最后一个走,因为他是班长,所以一定要确保走后教室灯关掉,门锁上;

  3. 其他6位同学随时可以走;

常用方法

  • CountDownLatch(int count); //构造方法,创建一个值为count 的计数器。
  • await();//阻塞当前线程,将当前线程加入阻塞队列。
  • await(long timeout, TimeUnit unit);//在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行,
  • countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。

代码证明

public class CountDownLatchTest {
    public static void main(String[] args) {
        testCase();
}
    public static void testCase() {
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i < 6; i++) {
            new Thread(() ->{
                System.out.println(Thread.currentThread().getName()+"同学\t 离开了教室");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        try {
            countDownLatch.await();
            System.out.println("班长最后走,并把教室灯关了,门锁住====");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出如下:

0同学	 离开了教室
3同学	 离开了教室
2同学	 离开了教室
1同学	 离开了教室
4同学	 离开了教室
5同学	 离开了教室
班长最后走,并把教室灯关了,门锁住====

CountDownLatch结合枚举的使用

现在模拟一个场景:秦始皇一统天下的过程。

  1. 首先,春秋时代,一个有六个国家,所以你给的参数不能在6之外;
  2. 六个国家先后被灭(此处忽略被灭顺序);
  3. 等六个国家全部灭了之后,秦始皇才一统天下,不能‘某一个国家被灭’出现在‘秦始皇已经一统天下’之后;

上代码:

枚举类:

public enum CountryEnum {
    ONE(1,"齐"),TWO(2,"楚"),TRHEE(3,"燕"),
    FOUR(4,"赵"),FIVE(5,"魏"),SIX(6,"韩");

    private Integer code;
    private String country;
	//私有构造
    CountryEnum(Integer code, String country) {
        this.code = code;
        this.country = country;
    }
	//对外公开的方法,用于匹配
    public static String getCountryByCode(Integer code){
        CountryEnum[] countryValues = CountryEnum.values();
        for(CountryEnum country:countryValues){
            if(code == country.getCode()){
                return country.getCountry();
            }
        }
        return null;
    }
	getGetter/Setter方法略...
}

测试类:

public class CountDownLatchTest {
    public static void main(String[] args) {
        actualUse();
    }

    public static void actualUse() {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() ->{
                System.out.println(Thread.currentThread().getName()+"国\t 被灭了...");
                countDownLatch.countDown();
            },CountryEnum.getCountryByCode(i)).start();
        }
        try {
            countDownLatch.await();
            System.out.println("秦国一统天下======");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出如下:

齐国	 被灭了...
魏国	 被灭了...
韩国	 被灭了...
燕国	 被灭了...
赵国	 被灭了...
楚国	 被灭了...
秦国一统天下======

结合枚举使用的好处:

其实我们可以反过来推,如果这种情况不使用枚举,他会怎么写?

肯定是在线程内部去判断code值,类似这种:

if(i == 1){
	System.out.println("齐国被灭了...")
}else if(i == 1){
	System.out.println("楚国被灭了...")
}else if(){
...
}

这种问题在5个线程下还好,但如果50个呢,500个呢,这样就很难维护。

枚举不仅可以简化我们的代码,降低维护成本,而且还可以限制传参,比如,在当前代码里,你传一个7,他肯定是错误的。

综上所述,使用枚举的好处:

  1. 简化代码,降低后期维护成本;
  2. 限制传参类型,防止部分恶意传参。

CountDownLatch原理

由于这里本人对AQS的原理暂时还不清楚,所以这里不做详细展示,

后期待详细了解之后,再来做详细补充…

本文的原理这块都是总结性的东西,如想详细研究,你可以通过其他资料查阅。

CountDownLatch是通过维护一个计数器,然后通过AQS去实现阻塞与唤醒机制。

当我们调用CountDownLatch countDownLatch = new CountDownLatch(N)的时候,他会将这个N传递给AQS队列的state,而这个state的值代表CountDownLatch所剩余的计数次数。

 public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);//创建同步队列,并设置初始计数器值(Sync为AQs的实现)
    }

调用await()的时候,会创建一个节点,加入到AQS阻塞队列,并同时把当前线程挂起。

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

调用countDownLatch.down()的时候,会对计数器进行减一操作,一旦为零,则唤醒所有阻塞队列里面的线程。

CyclicBarrier

什么是CyclicBarrier呢?

CyclicBarrierN个线程相互等待,任何一个线程在未完成之前,所有的线程都必须等待。

还是老样子,举个通俗易懂的例子把。

模拟场景:待收集完七颗龙珠,即可召唤神龙,缺任何一颗,都不行。

常用方法

//构造方法1
public CyclicBarrier(int parties) {
    this(parties, null);
}
 //构造方法2
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}
//await方法
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        // 不超时等待
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

代码证明

public class CyclicbarrierTest {

    public static final Integer CYCLICBARRIER_SIZE = 7;

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(CYCLICBARRIER_SIZE,()->{
            System.err.println("七颗龙珠已集齐,出来吧,神龙!");
        });
        for (int i = 1; i <= 7; i++) {
            new Thread(() ->{
                try {
                    System.out.println("第"+Thread.currentThread().getName()+"颗龙珠已集齐!");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

输出结果:

1颗龙珠已集齐!
第5颗龙珠已集齐!
第4颗龙珠已集齐!
第7颗龙珠已集齐!
第2颗龙珠已集齐!
第3颗龙珠已集齐!
第6颗龙珠已集齐!
七颗龙珠已集齐,出来吧,神龙!

CyclicBarrier原理

本文的原理这块都是总结性的东西,如想详细研究,你可以通过其他资料查阅。

基于ReentrantLock和Condition来实现的。

Semaphore

什么是semaphore呢?

semaphroe:信号量,一种共享锁,指的是控制某个资源被访问的线程数

模拟场景:

  1. 停车场共有3个车位,在同一时间内,有且仅能停3辆车;
  2. 超出3辆车的部分则阻塞,即等待;
  3. 当3个车位中有车驶出时,下面等待的车 则进来使用。

常用方法

acquire() / acquire(int permits) 
获取1/多个凭证,如果凭证数量不够,等待其他线程释放(在未获取到凭证之前、或者被其他线程调用中断之前,该线程一直处于阻塞状态。)
注意:此处的permits代表一个线程要占用2个凭证,如果总凭证为10,permits=2,只能同时5个线程占用。
​    
acquireUninterruptibly() 
获取一个凭证,在获取到凭证之前线程一直处于阻塞状态(忽略中断)。
    
tryAcquire() / tryAcquire(int permits)
尝试获得凭证,返回获取凭证成功或失败,不阻塞线程。
​
tryAcquire(long timeout, TimeUnit unit)
尝试获得凭证,在超时时间内循环尝试获取,限时等待。
​
release() / release(int permits)
释放1/多个凭证,唤醒1/多个获取凭证不成功的阻塞线程。
​
hasQueuedThreads()
等待队列里是否还存在等待线程。
​
getQueueLength()
获取等待队列里阻塞的线程数。
​
drainPermits()
清空凭证把可用凭证数置为0,返回清空凭证的数量。
​
availablePermits()
返回可用的凭证数量。

代码证明

public class SemaphoreTest {
    public static final int SEMAPHORE_SIZE = 4;

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(SEMAPHORE_SIZE);
        for (int i = 1; i <= 8; i++) {
            new Thread(() ->{
                try {
                    System.out.println(Thread.currentThread().getName()+"车\t 来到了停车场 ");
                    if(semaphore.availablePermits()==0){
                        System.out.println("车位不足,请耐心等待");
                    }
                    semaphore.acquire();
                    System.err.println(Thread.currentThread().getName()+"车\t抢到了车位");
                    try{ TimeUnit.SECONDS.sleep(3);}catch(Exception e){e.getStackTrace();};
                    System.out.println(Thread.currentThread().getName()+"车\t休息了3秒,开走了===");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally{
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

输出结果:

1车	 来到了停车场 
5车	 来到了停车场 
6车	 来到了停车场 
4车	 来到了停车场 
2车	 来到了停车场 
车位不足,请耐心等待
3车	 来到了停车场 
车位不足,请耐心等待
8车	 来到了停车场 
车位不足,请耐心等待
7车	 来到了停车场 
车位不足,请耐心等待
1车	抢到了车位
5车	抢到了车位
6车	抢到了车位
4车	抢到了车位
1车	休息了3秒,开走了===
4车	休息了3秒,开走了===
5车	休息了3秒,开走了===
6车	休息了3秒,开走了===
2车	抢到了车位
8车	抢到了车位
7车	抢到了车位
3车	抢到了车位
2车	休息了3秒,开走了===
7车	休息了3秒,开走了===
8车	休息了3秒,开走了===
3车	休息了3秒,开走了===

Semapore原理

由于这里本人对AQS的原理暂时还不清楚,所以这里不做详细展示,

后期待详细了解之后,再来做详细补充…

本文的原理这块都是总结性的东西,如想详细研究,你可以通过其他资料查阅。

跟CountDownLatch一样,也是维护了一个继承了AQS的Sync同步器,对线程的控制均通过sync来实现。

初始化时 Semaphore semaphore=new Semaphore(n)

默认一个非公平锁的同步阻塞队列,他会将这个N传递给AQS队列的state,代表我要申请凭证的数量。

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

acquire/realease则是线程的唤醒与阻塞机制…

拓展

CyclicBarrier与CountDownLatch的区别

  1. 对于CountDownLatch来说,他的侧重点在于一个线程在等待,而另外那N的线程在把**“某个事情”**做完之后可以继续等待,可以终止。
    对于CyclicBarrier来说,他的侧重点是在于N个线程,如果其中任何一个没有完成,所有的线程都必须等待。
  2. CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;

另:在semaphore与countDownLatch的比较中,前者可重复利用,就跟停车一样,只要资源还在,就可以重复,但countDownLatch不可以,当count减完之后,不可再利用。

总结

其实本文只是一个简单的使用,对于涉及到原理部分,由于暂时对AQS了解不深,不敢妄加评论。

Renference

关于java多线程浅析六: CyclicBarrier的原理分析和使用

CountDownLatch的使用和原理解析

Semaphore 使用及原理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值