JUC并发工具类在大厂的应用场景详解

一、常用并发同步工具类的真实应用场景

jdk提供了比synchronized更加高级的各种同步工具,包括ReentrantLockSemaphoreCountDownLatchCyclicBarrier等,可以实现更加丰富的多线程操作。

二、 ReentrantLock

ReentrantLock是一种可重入的独占锁,它允许同一个线程多次获取同一个锁而不会被阻塞。它的功能类似于synchronized是一种互斥锁,可以保证线程安全。相对于 synchronized,ReentrantLock具备如下特点:
1.可中断
2.可以设置超时时间
3.可以设置为公平锁
4.支持多个条件变量
5.与 synchronized 一样,都支持可重入

它的主要应用场景是在多线程环境下对共享资源进行独占式访问,以保证数据的一致性和安全性

ReentrantLock实现了Lock接口规范,常见API如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:在finally中解锁是为了在线程异常的情况下也能去释放锁

在使用时要注意 4 个问题:
1.默认情况下 ReentrantLock 为非公平锁而非公平锁;
2.加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常;
3.加锁操作一定要放在 try 代码之前,这样可以避免未加锁成功又释放锁的异常;
4.释放锁一定要放在 finally 中,否则会导致线程阻塞。

ReentrantLock使用

独占锁:模拟抢票场景
8张票,10个人抢,如果不加锁,会出现什么问题?
在这里插入图片描述
在这里插入图片描述
不加锁的效果: 出现超卖的问题
在这里插入图片描述

在这里插入图片描述
加锁效果: 正常,两个人抢票失败
在这里插入图片描述

公平锁和非公平锁

ReentrantLock支持公平锁和非公平锁两种模式
公平锁:线程在获取锁时,按照等待的先后顺序获取锁
非公平锁:线程在获取锁时,不按照等待的先后顺序获取锁,而是随机获取锁ReentrantLock默认是非公平锁

比如买票的时候就有可能出现插队的场景,允许插队就是非公平锁,如下图:
在这里插入图片描述

可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLocksynchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。 在实际开发中,可重入锁常常应用于递归操作、调用同一个类中的其他方法、锁嵌套等场景中。
在这里插入图片描述
在这里插入图片描述

结合Condition实现生产者消费者模式

java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal()Condition.signalAll() 方法唤醒等待的线程。
注意:调用Condition的await()和signal()方法,都必须在lock保护之内。

案例:基于ReentrantLock和Condition实现一个简单队列
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

应用场景总结

解决多线程竞争资源的问题,例如多个线程同时对同一个数据库进行写操作,可以使用ReentrantLock保证每次只有一个线程能够写入。
实现多线程任务的顺序执行,例如在一个线程执行完某个任务后,再让另一个线程执行任务。
实现多线程等待/通知机制,例如在某个线程执行完某个任务后,通知其他线程继续执行任务。

三、Semaphore

Semaphore(信号量)是一种用于多线程编程的同步工具,用于控制同时访问某个资源的线程数量。
在这里插入图片描述
Semaphore维护了一个计数器,线程可以通过调用acquire()方法来获取Semaphore中的许可证,当计数器为0时,调用acquire()的线程将被阻塞,直到有其他线程释放许可证;线程可以通过调用release()方法来释放Semaphore中的许可证,这会使Semaphore中的计数器增加,从而允许更多的线程访问共享资源。

在这里插入图片描述
permits 表示许可证的数量(资源数)
fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程

常用方法
acquire() 表示阻塞并获取许可
tryAcquire() 方法在没有许可的情况下会立即返回 false,要获取许可的线程不会阻塞
release() 表示释放许可

Semaphore使用

Semaphore实现服务接口限流
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Semaphore实现数据库连接池

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
应用场景总结
限流:Semaphore可以用于限制对共享资源的并发访问数量,以控制系统的流量。
资源池:Semaphore可以用于实现资源池,以维护一组有限的共享资源。

四、CountDownLatch

CountDownLatch(闭锁)是一个同步协助类,允许一个或多个线程等待,直到其他线程完成操作集
在这里插入图片描述
CountDownLatch使用给定的计数值(count)初始化。await方法会阻塞直到当前的计数值(count),由于countDown方法的调用达到0,count为0之后所有等待的线程都会被释放,并且随后对await方法的调用都会立即返回。这是一个一次性现象 —— count不会被重置。
在这里插入图片描述
在这里插入图片描述

CountDownLatch使用

模拟实现百米赛跑
在这里插入图片描述

在这里插入图片描述

多任务完成后合并汇总

很多时候,我们的并发任务,存在前后依赖关系;比如数据详情页需要同时调用多个接口获取数据,并发请求获取到数据后、需要进行结果合并;或者多个数据操作完成后,需要数据check。

在这里插入图片描述

应用场景总结

并行任务同步:CountDownLatch可以用于协调多个并行任务的完成情况,确保所有任务都完成后再继续执行下一步操作
多任务汇总:CountDownLatch可以用于统计多个线程的完成情况,以确定所有线程都已完成工作。
资源初始化:CountDownLatch可以用于等待资源的初始化完成,以便在资源初始化完成后开始使用

五、CyclicBarrier

CyclicBarrier(回环栅栏或循环屏障),是 Java 并发库中的一个同步工具,通过它可以实现让一组线程等待至某个状态(屏障点)之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

CyclicBarrier使用

模拟人满发车
利用CyclicBarrier的计数器能够重置,屏障可以重复使用的特性,可以支持类似“人满发车”的场景
在这里插入图片描述
在这里插入图片描述
多线程批量处理数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

应用场景总结

多线程任务:CyclicBarrier 可以用于将复杂的任务分配给多个线程执行,并在所有线程完成工作后触发后续操作。
数据处理:CyclicBarrier 可以用于协调多个线程间的数据处理,在所有线程处理完数据后触发后续操作

CyclicBarrier 与 CountDownLatch 区别
CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。

六、Exchanger

Exchanger是一个用于线程间协作的工具类,用于两个线程间交换数据。具体交换数据是通过exchange方法来实现的,如果一个线程先执行exchange方法,那么它会同步等待另一个线程也执行exchange方法,这个时候两个线程就都达到了同步点,两个线程就可以交换数据。
在这里插入图片描述
在这里插入图片描述

V exchange(V v):等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。
V exchange(V v, long timeout, TimeUnit unit):等待另一个线程到达此交换点,或者当前线程被中断——抛出中断异常;又或者是等候超时——抛出超时异常,然后将给定的对象传送给该线程,并接收该线程的对象。

模拟交易场景
用一个简单的例子来看下Exchanger的具体使用。两方做交易,如果一方先到要等另一方也到了才能交易,交易就是执行exchange方法交换数据。
在这里插入图片描述
在这里插入图片描述
模拟对账场景
在这里插入图片描述
在这里插入图片描述
模拟队列中交换数据场景
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

应用场景总结

数据交换:在多线程环境中,两个线程可以通过 Exchanger 进行数据交换。
数据采集:在数据采集系统中,可以使用 Exchanger 在采集线程和处理线程间进行数据交换

七、Phaser

Phaser(阶段协同器)是一个Java实现的并发工具类,用于协调多个线程的执行。它提供了一些方便的方法来管理多个阶段的执行,可以让程序员灵活地控制线程的执行顺序和阶段性的执行Phaser可以被视为CyclicBarrier和CountDownLatch的进化版,它能够自适应地调整并发线程数,可以动态地增加或减少参与线程的数量。所以Phaser特别适合使用在重复执行或者重用的情况
在这里插入图片描述

构造方法
Phaser(): 参与任务数0
Phaser(int parties) :指定初始参与任务数
Phaser(Phaser parent) :指定parent阶段器, 子对象作为一个整体加入parent对象, 当子对象中没有参与者时,会自动从parent对象解除注册
Phaser(Phaser parent,int parties) : 集合上面两个方法

增减参与任务数方法
int register() 增加一个任务数,返回当前阶段号。
int bulkRegister(int parties) 增加指定任务个数,返回当前阶段号。
int arriveAndDeregister() 减少一个任务数,返回当前阶段号。

到达、等待方法
int arrive() 到达(任务完成),返回当前阶段号。
int arriveAndAwaitAdvance() 到达后等待其他任务到达,返回到达阶段号。
int awaitAdvance(int phase) 在指定阶段等待(必须是当前阶段才有效)
int awaitAdvanceInterruptibly(int phase) 阶段到达触发动作
int awaitAdvanceInterruptiBly(int phase,long timeout,TimeUnit unit)
protected boolean onAdvance(int phase,int registeredParties)类似CyclicBarrier的触发命令,通过重写该方法来增加阶段到达动作,该方法返回true将终结Phaser对象。

多线程批量处理数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
阶段性任务:模拟公司团建


import java.util.Random;
import java.util.concurrent.Phaser;

public class PhaserDemo {
    public static void main(String[] args) {
        final Phaser phaser = new Phaser() {
            //重写该方法来增加阶段到达动作
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                // 参与者数量,去除主线程
                int staffs = registeredParties - 1;
                switch (phase) {
                    case 0:
                        System.out.println("大家都到公司了,出发去公园,人数:" + staffs);
                        break;
                    case 1:
                        System.out.println("大家都到公园门口了,出发去餐厅,人数:" + staffs);
                        break;
                    case 2:
                        System.out.println("大家都到餐厅了,开始用餐,人数:" + staffs);
                        break;

                }

                // 判断是否只剩下主线程(一个参与者),如果是,则返回true,代表终止
                return registeredParties == 1;
            }
        };

        // 注册主线程 ———— 让主线程全程参与
        phaser.register();
        final StaffTask staffTask = new StaffTask();

        // 3个全程参与团建的员工
        for (int i = 0; i < 3; i++) {
            // 添加任务数
            phaser.register();
            new Thread(() -> {
                try {
                    staffTask.step1Task();
                    //到达后等待其他任务到达
                    phaser.arriveAndAwaitAdvance();

                    staffTask.step2Task();
                    phaser.arriveAndAwaitAdvance();

                    staffTask.step3Task();
                    phaser.arriveAndAwaitAdvance();

                    staffTask.step4Task();
                    // 完成了,注销离开
                    phaser.arriveAndDeregister();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        // 两个不聚餐的员工加入
        for (int i = 0; i < 2; i++) {
            phaser.register();
            new Thread(() -> {
                try {
                    staffTask.step1Task();
                    phaser.arriveAndAwaitAdvance();

                    staffTask.step2Task();
                    System.out.println("员工【" + Thread.currentThread().getName() + "】回家了");
                    // 完成了,注销离开
                    phaser.arriveAndDeregister();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        while (!phaser.isTerminated()) {
            int phase = phaser.arriveAndAwaitAdvance();
            if (phase == 2) {
                // 到了去餐厅的阶段,又新增4人,参加晚上的聚餐
                for (int i = 0; i < 4; i++) {
                    phaser.register();
                    new Thread(() -> {
                        try {
                            staffTask.step3Task();
                            phaser.arriveAndAwaitAdvance();

                            staffTask.step4Task();
                            // 完成了,注销离开
                            phaser.arriveAndDeregister();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }).start();
                }
            }
        }
    }

    static final Random random = new Random();

    static class StaffTask {
        public void step1Task() throws InterruptedException {
            // 第一阶段:来公司集合
            String staff = "员工【" + Thread.currentThread().getName() + "】";
            System.out.println(staff + "从家出发了……");
            Thread.sleep(random.nextInt(5000));
            System.out.println(staff + "到达公司");
        }

        public void step2Task() throws InterruptedException {
            // 第二阶段:出发去公园
            String staff = "员工【" + Thread.currentThread().getName() + "】";
            System.out.println(staff + "出发去公园玩");
            Thread.sleep(random.nextInt(5000));
            System.out.println(staff + "到达公园门口集合");

        }

        public void step3Task() throws InterruptedException {
            // 第三阶段:去餐厅
            String staff = "员工【" + Thread.currentThread().getName() + "】";
            System.out.println(staff + "出发去餐厅");
            Thread.sleep(random.nextInt(5000));
            System.out.println(staff + "到达餐厅");

        }

        public void step4Task() throws InterruptedException {
            // 第四阶段:就餐
            String staff = "员工【" + Thread.currentThread().getName() + "】";
            System.out.println(staff + "开始用餐");
            Thread.sleep(random.nextInt(5000));
            System.out.println(staff + "用餐结束,回家");
        }
    }
}
员工【Thread-0】从家出发了……
员工【Thread-1】从家出发了……
员工【Thread-2】从家出发了……
员工【Thread-3】从家出发了……
员工【Thread-4】从家出发了……
员工【Thread-2】到达公司
员工【Thread-3】到达公司
员工【Thread-4】到达公司
员工【Thread-1】到达公司
员工【Thread-0】到达公司
大家都到公司了,出发去公园,人数:5
员工【Thread-0】出发去公园玩
员工【Thread-1】出发去公园玩
员工【Thread-4】出发去公园玩
员工【Thread-3】出发去公园玩
员工【Thread-2】出发去公园玩
员工【Thread-3】到达公园门口集合
员工【Thread-3】回家了
员工【Thread-2】到达公园门口集合
员工【Thread-1】到达公园门口集合
员工【Thread-0】到达公园门口集合
员工【Thread-4】到达公园门口集合
员工【Thread-4】回家了
大家都到公园门口了,出发去餐厅,人数:3
员工【Thread-0】出发去餐厅
员工【Thread-1】出发去餐厅
员工【Thread-2】出发去餐厅
员工【Thread-5】出发去餐厅
员工【Thread-6】出发去餐厅
员工【Thread-7】出发去餐厅
员工【Thread-8】出发去餐厅
员工【Thread-1】到达餐厅
员工【Thread-8】到达餐厅
员工【Thread-7】到达餐厅
员工【Thread-5】到达餐厅
员工【Thread-6】到达餐厅
员工【Thread-0】到达餐厅
员工【Thread-2】到达餐厅
大家都到餐厅了,开始用餐,人数:7
员工【Thread-2】开始用餐
员工【Thread-0】开始用餐
员工【Thread-8】开始用餐
员工【Thread-6】开始用餐
员工【Thread-7】开始用餐
员工【Thread-1】开始用餐
员工【Thread-5】开始用餐
员工【Thread-8】用餐结束,回家
员工【Thread-1】用餐结束,回家
员工【Thread-7】用餐结束,回家
员工【Thread-0】用餐结束,回家
员工【Thread-5】用餐结束,回家
员工【Thread-2】用餐结束,回家
员工【Thread-6】用餐结束,回家

进程已结束,退出代码0

应用场景总结

多线程任务分配:Phaser 可以用于将复杂的任务分配给多个线程执行,并协调线程间的合作。
多级任务流程:Phaser 可以用于实现多级任务流程,在每一级任务完成后触发下一级任务的开始。
模拟并行计算:Phaser 可以用于模拟并行计算,协调多个线程间的工作。
阶段性任务:Phaser 可以用于实现阶段性任务,在每一阶段任务完成后触发下一阶段任务的开始。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值