icode icode
首页
  • Android学习

    • 📁基础内容
    • 📺AndroidCore
    • 🎨Android-UI
    • 🏖️Components
    • 📊Fragment
    • 🔗网络操作
    • 🔏异步机制
    • 📦数据存储
    • 🗃️Gradle
  • 学习笔记

    • 『框架』笔记
    • 『Kotlin』笔记
    • 《Vue》笔记
    • 《Git》学习笔记
    • 『Bug踩坑记录』
  • ListView
  • RecyclerView
  • ViewPager
  • Java笔记

    • 🟠JavaSE
    • 🟢JavaWeb
    • 🔴JavaEE
    • ⚪JavaTopic
    • 🍳设计模式
  • 计算机基础

    • 📌计算机网络
    • 🔍数据结构
    • 📦数据库
    • 💻OS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 关于

    • 📫关于我
  • 收藏

    • 网站
    • 资源
    • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

iqqcode

保持对技术的探索实践与热爱
首页
  • Android学习

    • 📁基础内容
    • 📺AndroidCore
    • 🎨Android-UI
    • 🏖️Components
    • 📊Fragment
    • 🔗网络操作
    • 🔏异步机制
    • 📦数据存储
    • 🗃️Gradle
  • 学习笔记

    • 『框架』笔记
    • 『Kotlin』笔记
    • 《Vue》笔记
    • 《Git》学习笔记
    • 『Bug踩坑记录』
  • ListView
  • RecyclerView
  • ViewPager
  • Java笔记

    • 🟠JavaSE
    • 🟢JavaWeb
    • 🔴JavaEE
    • ⚪JavaTopic
    • 🍳设计模式
  • 计算机基础

    • 📌计算机网络
    • 🔍数据结构
    • 📦数据库
    • 💻OS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 关于

    • 📫关于我
  • 收藏

    • 网站
    • 资源
    • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaSE

    • 面向对象

    • 常用API

    • 集合类

    • 多线程

      • Thread

      • JUC

        • JUC包下的常用工具类
          • JUC包下常用的辅助类
          • 阻塞队列,同步队列
          • Java内存模型JMM详解
          • volatile
          • CAS自旋
          • CAS与volatile无锁解决并发
          • AQS
          • 线程池
          • Future接口
      • JDK8新特性

      • IO流

      • JVM

    • JavaWeb

    • JavaEE

    • JavaTopic

    • 设计模式

    • 计算机基础

    • Java后端
    • JavaSE
    • 多线程
    • JUC
    iqqcode
    2021-06-17
    目录

    JUC包下的常用工具类

    这篇文章主要是关于 java.util.concurrent(JUC) 类包下的常用类


    JUC是JDK5才引入的并发类库. JUC中为了满足在并发编程中不同的需求,提供了几个工具类供我们使用,分别是

    CountDownLatch,CyclicBarrier,Semaphore和 Exchanger 下面分别进行简单的介绍.

    # 1. CountDownLatch-闭锁

    CountDownLatch类位于java.util.concurrent包下,利⽤用它可以实现类似计数器的功能.

    CountDownLatch 的主要作用是利用计数来保证线程的执行顺序,有点像倒计时,当计数为0时某个线程才能开始执行

    CountDownLatch类只提供了⼀一个构造器:

    public CountDownLatch(int count) { }; //参数count为计数值
    
    1

    CountDownLatch中的常用方法,包括:

    • CountDownLatch(int count) : 构造方法,需要传入计数的初始值
    • void await() :等待线程调⽤用await()方法的线程会被一直被阻塞,它会等待直到count计数器的值为0才继续执行
    • boolean await(long timeout, TimeUnit unit) : 同上,但是加入了超时参数,如果超时了计数还不为0,也会照样执行,避免了一直阻塞
    • void countDown() : 计数减一

    • 闭锁 : 每个 CountDowmLatch对象的计数器在值减为0时不可恢复原值

    使用场景

    比如有一个任务A,它要等待其他3个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能.

    在这里插入图片描述

    模拟三个运动员跑步的场景

    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Author: Mr.Q
     * @Date: 2019-08-12 13:56
     * @Description:CountDownLatch阻塞线程,等到子线程减到相应count主线程才继续执行
     */
    class CountDownTest implements Runnable {
        private CountDownLatch countDownLatch;
    
        public CountDownTest(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() +"已经到达终点");
            countDownLatch.countDown();
        }
    }
    
    public class CountDownLatchTest {
        public static void main(String[] args) throws InterruptedException {
            // 主线程等待三个子线程减为 0再恢复执行
            //参数count为计数值
            CountDownLatch countDownLatch = new CountDownLatch(3); 
            System.out.println("Game start...");
            new Thread(new CountDownTest(countDownLatch),"Runner A").start();
            TimeUnit.SECONDS.sleep(1);
            new Thread(new CountDownTest(countDownLatch),"Runner B").start();
            TimeUnit.SECONDS.sleep(1);
            new Thread(new CountDownTest(countDownLatch),"Runner C").start();
            // 主线程阻塞,等待 Runner A,B,C都到达终点后再执行
            countDownLatch.await();
            System.out.println("All Runners have reached destination.\nGame end!");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    运行结果:

    如果在输出 All Runners have reached destination.\nGame end!之前不调用 await()

    在这里插入图片描述

    则运行结果会出现:

    在这里插入图片描述

    此时主线程和A,B,C 三个线程并行,没有调用await()的主线程将不再阻塞.

    # 2. CyclicBarrier-循环栅栏

    循环栅栏 :

    • 循环:每个CyclicBarrier的对象可以重复使用
    • 栅栏 : 每个子线程都阻塞,让一组线程同时到达某个时间节点

    使用场景
    例如三个子线程 A,B,C 在分别写入数据,其中 A线程写完数据后会阻塞,等待 B,C线程也写完数据后,才恢复执行;此时主线程才能读数据

    在这里插入图片描述

    CyclicBarrier提供2个构造器器:

    • public CyclicBarrier(int parties) {}

    所有子线程调用await()后,将计数器值减1并进入==阻塞状态==; 直到计数器值减为0时,所有调用await()阻塞的子线程再同时恢复执行

    • public CyclicBarrier(int parties, Runnable barrierAction) {}

    所有调用await()阻塞的子线程在计数器值减为0后,随机挑选一个线程执行 barrierAction 任务后,所有子线程恢复执行

    CyclicBarrier中常用的方法:

    int await() : 挂起当前线程,直到所有线程组中的线程都完成后继续执行,返回当前线程到达次序

    int await(long timeout, TimeUnit unit) : 加了一个超时参数

    我们来拿做饭举个例子,众所周知,在脱单这一环节,会做饭的蓝人,在广大只会敲代码的搬砖工面前是有绝对优势的,有空了我一定要习得一手好厨艺...

    大葱牛肉饺子做法 (opens new window)

    以做饺子为例,A,B,C三道工序

    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Author: Mr.Q
     * @Date: 2019-08-12 15:02
     * @Description:所有调用await()阻塞的子线程在计数器值减为 0后
     * 随机挑选一个线程执行 barrierAction任务后,所有子线程恢复执行
     */
    class CyclicActionTest implements Runnable {
        private CyclicBarrier cyclicBarrier;
    
        public CyclicActionTest(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "做饺子中...");
            try {
                TimeUnit.SECONDS.sleep(3);
                // 所有子线程再次都阻塞
                cyclicBarrier.await();
                System.out.println("所有操作都已完成!\t开吃...");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
    public class CyclicBarrierActionTest {
        public static void main(String[] args) throws InterruptedException {
            // 传入线程组的数量和当线程达到时间节点后要做的操作
            CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
                @Override
                public void run() {
                    System.out.println("\n哪个环节最重要?: "+Thread.currentThread().getName()+"\n");
                }
            });
            new Thread(new CyclicTest(cyclicBarrier),"A 和面中,勿扰 ").start();
            TimeUnit.SECONDS.sleep(1);
            new Thread(new CyclicTest(cyclicBarrier),"B 剁饺子馅,包饺子").start();
            TimeUnit.SECONDS.sleep(1);
            new Thread(new CyclicTest(cyclicBarrier),"C 下锅煮饺子").start();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46

    待 A,B,C分别完成任务后,饺子才可以吃.

    执行内容好像忘记改了,但是步骤是一样的

    执行过程分析:

    1. A和完面之后便阻塞
    2. 此后B剁饺子馅,包饺子,完成之后也阻塞
    3. A,B此时在阻塞中,C下锅煮饺子
    4. 调用await()阻塞的子线程在计数器值减为0后(即A,B,C执行完之后,随机挑选一个线程执行 barrierAction任务(new Runnable(),哪个环节最重要?)
    5. 然后所有子线程恢复执行

    # 3. Semaphore-信号量

    Semaphore用于控制信号量的个数,构造时传入个数. 总数就是控制并发的数量.

    控制并发的数量假如是5,程序执行前用acquire()方法获得信号,则可用信号变为4,程序执行完通过release()方法归还信号量,可用信号又变为5.

    如果可用信号为0,acquire就会造成阻塞,等待release释放信号. acquire()和release()可以不在同一个线程使用.


    Semaphore实现的功能就类似厕所有5个坑位,假如有10个人要上厕所,那么同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了.

    另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项. 单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合.


    Semaphore类提供了2个构造器:

    • 参数permits表示许可数目,即同时可以允许多少线程进行访问 在这里插入图片描述
    • 多了⼀个参数fair表示是否是公平的,即等待时间越久的越先获取许可 在这里插入图片描述

    Semaphore类中常用的方法

    • acquire() : 用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可
    • 获取一个许可在这里插入图片描述获取permits个许可 在这里插入图片描述
    • release() : 用来释放许可;(在释放许可之前,必须先获得许可)
    • 释放一个许可 在这里插入图片描述 释放permits个许可 在这里插入图片描述

    8说了,网吧开黑走起!!!

    import java.util.concurrent.Semaphore;
    
    /**
     * @Author: Mr.Q
     * @Date: 2019-08-12 16:25
     * @Description:Semaphore
     */
    
    // 网吧开黑
    class SemaphoreDemo implements Runnable {
        private Semaphore semaphore;
        private int num;
    
        public SemaphoreDemo(Semaphore semaphore, int num) {
            this.semaphore = semaphore;
            this.num = num;
        }
    
        @Override
        public void run() {
            //尝试申请设备
            try {
                semaphore.acquire();
                System.out.println("玩家"+ this.num +"使用一台电脑打游戏...");
                Thread.sleep(2000);
                System.out.println("玩家"+ this.num +"释放一台设备!");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public class SemaphoreTest {
        public static void main(String[] args) {
            //信号量为5
            Semaphore semaphore = new Semaphore(5); //5台设备
            for(int i = 0; i < 8; i++) { //8名玩家
                new Thread(new SemaphoreDemo(semaphore,(i+1))).start();
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41

    可以看到这一组线程是同步的去执行的

    # 4. Exchanger-线程数据交换器

    使用场景
    用于两个线程的数据交换(不能用于多个)

    在这里插入图片描述

    调用exchange()方法会阻塞当前线程,必须等到另外一个线程时才可进行数据交换 在这里插入图片描述

    期待在最美的年华,遇到最美的你...

    import java.util.concurrent.Exchanger;
    
    /**
     * @Author: Mr.Q
     * @Date: 2019-08-12 16:08
     * @Description:
     */
    public class ExchangerTest {
        public static void main(String[] args) {
            Exchanger<String> exchanger = new Exchanger<>();
            Thread boyThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        String boy = exchanger.exchange("我明白你会来,所以我等!\n");
                        System.out.println("The boy said : To the most beautiful you ↓ \n" + boy);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            boyThread.start();
    
            Thread girlThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        String girl = exchanger.exchange("一个人心头上的微风,吹到另外一个人生活里去时,是偶然还是必然?\n" +
                                "人生的理想,是情感的节制恰到好处,还是情感的放肆无边无涯?\n" +
                                "生命的取与,是昨天的好,当前的好,还是明天的好?");
                        System.out.println("\nThe girl said : To the world who knows me the most ↓ \n" + girl);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            girlThread.start();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

    boy 和 girl 说的话交换... 在这里插入图片描述

    文章部分内容参考自:

    • JUC包下的工具类CountDownLatch、CyclicBarrier和Semaphore (opens new window)

    • Java多线程---JUC包下的常见类 (opens new window)

    • boy 和 girl said 来自 沈从文

    编辑 (opens new window)
    上次更新: 2021/06/27, 10:49:09
    ThreadLocal
    JUC包下常用的辅助类

    ← ThreadLocal JUC包下常用的辅助类→

    最近更新
    01
    匿名内部类
    10-08
    02
    函数式接口
    10-08
    03
    ARouter-Kotlin踩坑
    10-05
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 iqqcode | MIT License | 备案号-京ICP备2021028793号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×