专栏首页汤圆学JavaJava并发工具篇
原创

Java并发工具篇


theme: juejin

highlight: an-old-hope


作者:汤圆

个人博客:javalover.cc

前言

随着天气的逐渐变热,整个人也开始浮躁不安

当然这里说的不是我,因为我是一个比较安静的人

讲的是隔壁的老大哥,在训斥年幼的孩子

一通吼叫过后,男人安静了下来,孩子也哭个不停

简介

前面我们介绍了 JUC 中的并发容器,它相当于一个同步容器的升级版,很大程度上提高了并发的性能

今天我们来介绍 JUC 中的并发工具,**它主要是通过改变自身的状态来控制线程的执行流程**;

常见的有如下几种:

  • **CountDownLatch**:倒计时器(属于闭锁的一种实现),用来阻塞线程
  • **CyclicBarrier**:循环栅栏,类似倒计时器,但是比他更高级,也是用来阻塞线程(只不过阻塞的方式不同,下面会具体介绍)
  • **Semaphore**:信号量,用来控制多个线程同时访问指定的资源,比如我们常用的数据库连接池

下面让我们开始吧

文章如果有问题,欢迎大家批评指正,在此谢过啦

目录

  1. 什么是并发工具
  2. 倒计数器 CountDownLatch
  3. 倒计数器升级版 CyclicBarrier【循环栅栏】
  4. 信号量 Semaphore
  5. 区别

正文

1. 什么是并发工具

并发工具是一组工具类,主要是用来控制线程的执行流程,比如阻塞某个线程,以等待其他线程

2. 倒计数器 CountDownLatch

从字面意思来看,就是一个倒计数门闩(shuan,打了半天zha就是打不出来)

通俗一点来说,就是倒计数,时间一到,门闩就打开

注:一旦打开,就不能再合上,即这个 CountDownLatch 的状态改变是永久不可恢复的(记住这个点,后面会有对比)

比较官方的说法:倒计数器用来阻塞某个(某些)线程,以等待其他多个线程的任务执行完成(以这个说法为准,上面的可以用来对比参考)

下面列出 CountDownLatch 的几个方法:

  • **构造方法**:public CountDownLatch(int count),其中count就是我们所说的内部状态(当count=0时,表示到达终止状态,此时会恢复被阻塞的线程)
  • **修改状态**:public void countDown(),该方法会递减上面的count状态,每执行一次,就-1;(当count=0时,表示到达终止状态,此时会恢复被阻塞的线程)
  • **等待**:public void await(),该方法会阻塞当前线程,直到count状态变为0,才会恢复执行(除非中断,此时会抛出中断异常)
  • **超时等待**:public boolean await(long timeout, TimeUnit unit),类似上面的await,只不过可以设置超时时间,等过了超时时间,还在阻塞,则直接恢复
  • **获取状态值 count**:public long getCount(),获取count的数值,以查看还可以递减多少次(多用来调试)

模拟场景的话,这里先列举三个,肯定还有其他的

  • 第一个就是计数器了,最直接的
  • 第二个就是统计任务执行时长
  • 第三个就是多人5V5游戏,等所有人加载完毕,就开始游戏

下面我们以第三个场景为例,写个例子:多人游戏加载画面

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {

        // 1. 构造一个倒计数器,给定一个状态值10

        CountDownLatch latch = new CountDownLatch(10);

        System.out.println("准备加载");

          // 这里我们创建10个线程,模拟 5V5 游戏的10个玩家

        for (int i = 0; i < 10; i++) {

            new Thread(()->{

                // 这里我们给点延时,模拟网络延时

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                System.out.println(Thread.currentThread().getName()+"加载100%");

                // 2. 这里的countDown就是用来改变倒计数器的内部状态,每次-1

                latch.countDown(); //这里不会阻塞当前线程,执行完后就立马返回了

            }).start();

        }

        // 3. 这里阻塞等待状态的完成,即10变为0;

        latch.await();

        System.out.println("所有人加载完成,开始游戏");

    }

}

输出如下:

准备加载

Thread-0加载100%

Thread-1加载100%

Thread-2加载100%

Thread-3加载100%

Thread-4加载100%

Thread-5加载100%

Thread-6加载100%

Thread-8加载100%

Thread-9加载100%

Thread-7加载100%

所有人加载完成,开始游戏

**这里倒计数器的作用就是阻塞主线程,以等待其他10个子线程,等到都准备好,再恢复主线程**

它的特点就是:一次性使用,达到终止状态后不能再改变

3. 倒计数器升级版 CyclicBarrier【循环栅栏】

循环栅栏,类似倒计数器,也是用来阻塞线程,不过它的重点在于**循环**使用

而倒计数器只能用一次(这属于他们之间最明显的一个区别)

PS:猜测之所以叫循环栅栏,而不是循环门闩,可能是因为栅栏的作用比门闩更强大,所以叫栅栏更适合吧

**官方说法:循环栅栏一般用来表示多个线程之间的相互等待(阻塞)**

比如有10个线程,都要await等待;那要等到最后一个线程await时,栅栏才会打开

如果有定义栅栏动作,那么当栅栏打开时,会执行栅栏动作

**栅栏动作就是:栅栏打开后需执行的动作,通过构造函数的Runnable参数指定,可选参数**,下面会介绍

这个属于循环栅栏和倒计数器的**第二个区别**:

  • **循环栅栏强调的是多个被阻塞线程之间的相互协作关系(等待)**
  • **而倒计数器强调的是单个(或多个)线程被阻塞,来等待其他线程的任务执行**

下面我们看几个循环栅栏 CyclicBarrier 内部的方法:

  • **构造方法**:public CyclicBarrier(int parties, Runnable barrierAction),第一个表示需等待(阻塞)的线程数,第二个barrierAction就是上面我们说的栅栏动作,即当最后一个线程也被阻塞时,就会触发这个栅栏动作(这个参数可选,如果没有,则不执行任何动作)
  • **等待**:public int await(),阻塞当前线程,直到最后一个线程被阻塞,才会恢复
  • **超时等待**:public boolean await(long timeout, TimeUnit unit),类似上面的await,只不过可以设置超时时间
  • **获取当前等待的线程数**:public int getNumberWaiting(),即调用了await方法的线程数量

场景:

  • 大事化小,小事合并:就是将某个大任务拆解为多个小任务,等到小任务都完成,再合并为一个结果
  • 多人对战游戏团战
  • 上面的倒计数器表示游戏开始前的准备工作(只需准备一次)
  • 而这里的循环栅栏则可以表示游戏开始后的团战工作(可团战多次)

下面看下例子:多人游戏团战画面

public class CyclicBarrierDemo {

    public static void main(String[] args) throws InterruptedException {



        // 1. 创建一个循环栅栏,给定等待线程数10和栅栏动作

        CyclicBarrier barrier = new CyclicBarrier(10,()->{

            // 栅栏动作,等到所有线程都await,就会触发

            System.out.println("=== 人齐了,开始团吧");

            try {

                Thread.sleep(2000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        });

        System.out.println("=== 准备第一波团战 ===");

        // 2. 创建10个线程,模拟10个玩家

        for (int i = 0; i < 10; i++) {

            new Thread(()->{

                try {

                    // 玩家到场

                    System.out.println(Thread.currentThread().getName()+"=>第一波团,我准备好了");

                    // 等待其他人,等人齐就可以团了(人齐了会执行栅栏动作,此时这边也会恢复执行)

                    barrier.await();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                } catch (BrokenBarrierException e) {

                    e.printStackTrace();

                }

            }).start();

        }

        // 3. 查询当前等待都线程数量,如果不为0,则主线程继续等待

        while (barrier.getNumberWaiting()!=0){

            Thread.sleep(1000);

        }

        System.out.println("=== 第一波团战结束 ===");

        

        // 4. 此时还可以进行第二波第三波团战。。。(循环栅栏可循环触发,倒计数器只能触发一次)

        

    }

}

输出如下:

=== 准备第一波团战 ===

Thread-0=>第一波团,我准备好了

Thread-1=>第一波团,我准备好了

Thread-2=>第一波团,我准备好了

Thread-3=>第一波团,我准备好了

Thread-4=>第一波团,我准备好了

Thread-5=>第一波团,我准备好了

Thread-6=>第一波团,我准备好了

Thread-7=>第一波团,我准备好了

Thread-8=>第一波团,我准备好了

Thread-9=>第一波团,我准备好了

=== 人齐了,开始团吧

=== 第一波团战结束 ===

4. 信号量 Semaphore

信号量主要是用来控制多个线程同时访问指定资源,比如数据库连接池,超过指定数量,就阻塞等待

下面我们介绍下信号量的几个关键方法:

  • 构造方法:public Semaphore(int permits, boolean fair),第一个参数为许可数,即允许同时访问的的线程数,第二个参数为公平还是非公平模式(默认非公平)
  • 公平模式,谁先调用acquire,谁就先访问资源,FIFO先进先出
  • 非公平模式,允许插队,如果某个线程刚释放了许可,另一个线程就调用了acquire,那么这个线程就会插队访问资源)
  • 获取许可:public void acquire(),如果有许可,则直接返回,并将许可数递减1;如果没可用的许可,就阻塞等待,或者被中断
  • 尝试获取许可:public boolean tryAcquire(),类似上面的acquire,但是不会被阻塞和中断,因为如果没有可用的许可,则直接返回false
  • 释放许可:public void release(),释放一个许可,并将许可数递增1
  • 获取可用的许可数量:public int availablePermits(),这个方法一般用来调试

场景:数据库连接池

信号量的特点就是可重复使用许可,所以像数据库连接池这种场景就很适合了

这里就不举例子了,就是多个线程acquire和release,获取许可时,如果没有就阻塞,如果有就立即返回

5 区别

用表格看比较方便点

| 区别 | CountDownLatch | CyclicBarrier | Semaphore |

| ---------- | ------------------------------------------------------------ | ---------------------------------------------- | ------------------ |

| 可使用次数 | 单次 | 多次(循环使用) | 多次(循环使用) |

| 线程的阻塞 | 阻塞单个(多个)线程,以等待其他线程的执行 | 多个线程之间的相互阻塞 | 超过许可数,会阻塞 |

| 场景 | 1. 计数器 2. 统计任务执行时长 3. 多人对战游戏的开局等待 | 1. 大事化小,再合并 2. 多人对战游戏的团战 | 1. 数据库连接池 |

可以看到,倒计数器主要是用来表示单个线程等待多个线程,而循环栅栏主要是用来表示多个线程之间的相互等待

总结

  1. 什么是并发工具:并发工具是一组工具类,主要是用来控制线程的执行流程,比如阻塞某个线程,以等待其他线程
  2. 倒计数器 CountDownLatch:用来表示阻塞某个(某些)线程,以等待其他多个线程的任务执行完成
  3. 循环栅栏 CyclicBarrier:用来表示多个线程之间的相互等待协作(阻塞)
  4. 信号量 Semaphore:用来表示允许同时访问指定资源的许可数(线程数)
  5. 区别:

| 区别 | CountDownLatch | CyclicBarrier | Semaphore |

| ---------- | ------------------------------------------------------------ | ---------------------------------------------- | ------------------ |

| 可使用次数 | 单次 | 多次(循环使用) | 多次(循环使用) |

| 线程的阻塞 | 阻塞单个(多个)线程,以等待其他线程的执行 | 多个线程之间的相互阻塞 | 超过许可数,会阻塞 |

| 场景 | 1. 计数器 2. 统计任务执行时长 3. 多人对战游戏的开局等待 | 1. 大事化小,再合并 2. 多人对战游戏的团战 | 1. 数据库连接池 |

参考内容:

  • 《Java并发编程实战》
  • 《实战Java高并发》

后记

学习之路,真够长,共勉之

写在最后:

**愿你的意中人亦是中意你之人**

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [Java并发系列]Java并发工具类

    用户2017109
  • Java并发-26.并发工具类-CyclicBarrier

    同步屏障CyclicBarrier让一组线程到达一个屏障(同步点)时阻塞,直到最后一个线程到达屏障时,屏障打开,所有线程继续执行。

    悠扬前奏
  • Java并发-27.并发工具类-Semaphore

    信号量Semaphore用来控制同时访问特定资源的线程数量,通过协调各个线程,保证公平合理的使用公共资源。

    悠扬前奏
  • Java并发-28.并发工具类-Exchanger

    悠扬前奏
  • Java并发-25.并发工具类-CountDownLatch

    悠扬前奏
  • Java并发-14.Lock工具

    悠扬前奏
  • java并发编程工具类JUC第八篇:ConcurrentHashMap

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口、ArrayBlockingQueue、DelayQueue、LinkedB...

    字母哥博客
  • 工具篇:JAVA开发之常用工具

    潜行前行
  • 【死磕Java并发】—–J.U.C之并发工具类:Semaphore

    信号量Semaphore是一个控制访问多个共享资源的计数器,和CountDownLatch一样,其本质上是一个“共享锁”。

    用户1655470
  • 【死磕Java并发】—– J.U.C之并发工具类:Semaphore

    芋道源码
  • 死磕Java并发:J.U.C之并发工具类:Semaphore

    信号量Semaphore是一个控制访问多个共享资源的计数器,和CountDownLatch一样,其本质上是一个“共享锁”。

    程序猿DD
  • 【死磕Java并发】—–J.U.C之并发工具类:Exchanger

    前面三篇博客分别介绍了CyclicBarrier、CountDownLatch、Semaphore,现在介绍并发工具类中的最后一个Exchange。Exchan...

    用户1655470
  • 【死磕Java并发】—–J.U.C之并发工具类:CountDownLatch

    在上篇博客中介绍了Java四大并发工具一直的CyclicBarrier,今天要介绍的CountDownLatch与CyclicBarrier有点儿相似。 Cyc...

    用户1655470
  • 【死磕Java并发】—–J.U.C之并发工具类:CyclicBarrier

    CyclicBarrier,一个同步辅助类,在API中是这么介绍的: 它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point...

    用户1655470
  • 【死磕Java并发】—- J.U.C之并发工具类:CyclicBarrier

    此篇博客所有源码均来自JDK 1.8 CyclicBarrier,一个同步辅助类,在API中是这么介绍的: 它允许一组线程互相等待,直到到达某个公共屏障点 (c...

    芋道源码
  • 【死磕 Java 并发】—– J.U.C 之并发工具类:Exchanger

    摘要: 原创出处 http://cmsblogs.com/?p=2269 「小明哥」欢迎转载,保留摘要,谢谢!

    芋道源码
  • 理解Java并发工具类CountDownLatch

    CountDownLatch相信大家并不陌生,我们在上篇文章中已经分析其实现,这里在简单回顾一下CountDownLatch是基于AQS共享锁构建的一种同步器,...

    我是攻城师
  • 理解Java并发工具类CyclicBarrier

    CyclicBarrier这个并发工具类和上篇文章中提到的CountDownLatch比较类似,可以把CyclicBarrier看做是可以可以复用的CountD...

    我是攻城师
  • 理解Java并发工具类Semaphore

    Semaphore是Java里面另外一个基本的并发工具包类,主要的的作用是用来保护共享资源的访问的,也就是仅仅允许一定数量的线程访问共享资源。Semaphore...

    我是攻城师

扫码关注云+社区

领取腾讯云代金券