【JAVA多线程】JDK线程同步工具类:Semaphore、CountDownLatch、CyclicBarrier

news2024/11/13 9:24:58

目录

1.可能会遇到的线程协作场景

2.Semaphore

3.CountDownLatch

4.CyclicBarrier


1.可能会遇到的线程协作场景

在并发编程中,线程除了独自向前运行,还可能相互之间要进行协作,以保证完成最终总的目标。可能会遇到的几种任务之间的协作:

  • 情景1:限定任务数

    • 由于资源有限,限制最多有多少个线程进行工作

  • 情景2:任务之间有依赖关系

    • 一个线程依赖于其它线程的执行结果,这个线程就必须等待其它线程执行完成才能继续往下走

  • 情景3:任务分阶段

    • 批量线程分阶段执行,每一个阶段是一个同步点,执行完的线程必须阻塞在同步点上等待同批的其它线程也执行完,再进入下一个阶段。由于阶段可能有多个,所以要用condition来实现。

  • 情景4:动态调整线程数量

    • 不管是情景2也好还是情景3也好,都有可能有动态调整线程的可能性

2.Semaphore

semaphore,信号量,用来解决情景1。

业务情景:

我们有一个资源,只允许最多 3 个线程同时访问。我们将使用 Semaphore 来实现这一功能。

代码示例:

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
​
public class SemaphoreExample {
​
    private static final int MAX_CONCURRENT_ACCESS = 3;
    private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_ACCESS);
​
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    // 获取许可
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 开始访问资源");
                    // 模拟耗时操作
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + " 结束访问资源");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.err.println(Thread.currentThread().getName() + " 被中断");
                } finally {
                    // 释放许可
                    semaphore.release();
                }
            }).start();
        }
    }
}

semaphore的实现底层其实就是AQS,没抢到资源的线程阻塞在队列中,也分了公平锁和非公平锁,其实现了AQS的共享模式tryAcquireShared(),每次去抢占资源的时候就对state做CAS减法。

acquire()去抢资源,没抢到或者抢失败了就把线程阻塞进CLH队列中:

最终调用到的是tryAcquireShared(),每次去抢占资源的时候就对state做CAS减法:

释放release()就不展开了,也是很简单的。

3.CountDownLatch

CountDownLatch,栅栏,用来解决情景2。

业务场景:

1个主线程需要等待10Worker线程完成工作才能退出。

这时候就要用CountDownLatch:

CountDownLatch countDownLatch=new CountDownLatch(10);
countDownLatch.await();//主线程阻塞在这里

其余Worker线程各自去:

countDownLatch.countDown();//每调用一次countDown,计数就会减1,减到0的时候主线程会被唤醒

countDownLatch也有一个继承AQS的Sync,countDown会去调用AQS共享模式的释放方法releaseShared()

releaseShared会CAS去对state进行-1,当发现state减到0后,会用doReleaseShared唤醒躺在CLH队列中的调用过await()的主线程:

4.CyclicBarrier

业务场景:

一共有10个线程,分阶段执行任务,每一个阶段必须所有10个线程都执行后,才能一同去执行下一个阶段的任务。

代码示例:

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
​
public class CyclicBarrierDemo {
​
    public static void main(String[] args) throws InterruptedException {
        // 创建一个 CyclicBarrier 实例
        CyclicBarrier barrier = new CyclicBarrier(10, () -> {
            System.out.println("All threads have arrived at the barrier. Moving to the next phase.");
        });
​
        // 启动 10 个线程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> work(barrier)).start();
        }
​
        // 等待所有线程完成第一个阶段
        TimeUnit.SECONDS.sleep(2);
​
        // 等待所有线程完成第二个阶段
        TimeUnit.SECONDS.sleep(2);
​
        // 等待所有线程完成第三个阶段
        TimeUnit.SECONDS.sleep(2);
    }
​
    private static void work(CyclicBarrier barrier) {
        try {
            // 模拟工作
            TimeUnit.SECONDS.sleep(1); // 暂停一段时间
​
            // 到达屏障
            barrier.await();
​
            // 模拟第二阶段的工作
            TimeUnit.SECONDS.sleep(1); // 暂停一段时间
​
            // 到达屏障
            barrier.await();
​
            // 模拟第三阶段的工作
            TimeUnit.SECONDS.sleep(1); // 暂停一段时间
​
            // 到达屏障
            barrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            Thread.currentThread().interrupt();
            System.err.println(Thread.currentThread().getName() + ": Interrupted or barrier broken.");
        }
    }
}

上面的代码模拟了三阶段的任务,没执行完一个阶段的任务线程就会调用CyclicBarrier的await()来等待其它的合作伙伴线程,要大家都达到后才会继续向下执行。可以看到CyclicBarrier的await()是线程同步的核心方法。一起来看看源码:

await()里面调用了doawait(),所以doawait才是核心方法:

来看看doawait()的源码:

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 获取当前的 Generation 对象
        final Generation g = generation;
​
        // 如果屏障已经损坏,则抛出 BrokenBarrierException
        if (g.broken)
            throw new BrokenBarrierException();
​
        // 如果线程被中断,则破坏屏障并抛出 InterruptedException
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
​
        // 减少 count 计数器的值,表示有一个线程到达了屏障
        int index = --count;
        
        // 如果 count 变为 0,这意味着所有线程都已经到达屏障
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                // 如果设置了屏障动作(回调函数),则执行该动作
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                
                // 开始新的屏障周期
                nextGeneration();
                
                // 返回 0 表示当前线程触发了屏障动作
                return 0;
            } finally {
                // 如果未执行屏障动作(回调函数),则破坏屏障
                if (!ranAction)
                    breakBarrier();
            }
        }
​
        // 循环等待其他线程到达
        for (;;) {
            //阻塞在condition上(trip是个condition),这样就能将lock释放出来,后面的线程可以继续争抢
            try {
                // 如果没有设置超时时间,则等待所有线程到达
                if (!timed)
                    trip.await();
                // 如果设置了超时时间,则等待所有线程到达或超时
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                // 如果线程被中断且屏障仍然有效,则破坏屏障并抛出 InterruptedException
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    // 如果线程即将完成等待,即使它被中断,也会忽略中断标志并将中断标记传递给后续执行
                    Thread.currentThread().interrupt();
                }
            }
​
            // 如果屏障已经损坏,则抛出 BrokenBarrierException
            if (g.broken)
                throw new BrokenBarrierException();
​
            // 如果屏障周期已经改变,则返回当前线程的索引
            if (g != generation)
                return index;
​
            // 如果设置了超时并且超时时间已到,则破坏屏障并抛出 TimeoutException
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        // 释放锁
        lock.unlock();
    }
}

其实可以看到上面的逻辑很简单,要是没到齐就先阻塞等待,要是到齐了就调用nextGeneration()去刷新轮次,这个方法里也就是一些资源的重置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2038443.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

『 C++ 』单例模式与IO流

文章目录 单例模式饿汉加载的单例模式实现懒汉加载的单例模式实现 IO流类型之间的转换 单例模式 单例模式是一种创建型设计模式; 确保一个类在应用程序的生命周期内仅有一个实例并提供一个全局访问点来访问该实例; 单例模式主要目的是为了控制某些类的实例化以避免产生多个实例…

Java面试--框架--Spring MVC

Spring MVC 目录 Spring MVC1.spring mvc简介2.spring mvc实现原理2.1核心组件2.2工作流程 3.RESTful 风格4.拦截器4.1过滤器、监听器、拦截器的对比4.2 拦截器基本概念4.3 拦截器的实现 1.spring mvc简介 Spring MVC是一款由Spring Framework 提供的 web组件&#xff0c;实现…

MySQL-MVCC举例说明

在数据库系统中&#xff0c;多版本并发控制&#xff08;MVCC, Multi-Version Concurrency Control&#xff09; 是一种用于提高并发性能的机制&#xff0c;它允许多个事务同时读取和写入数据&#xff0c;而不会产生锁等待和阻塞的问题。MySQL 的 InnoDB 存储引擎广泛使用了 MVC…

C#MVC返回DataTable到前端展示。

很久没写博客了&#xff0c;闭关太久&#xff0c;失踪人口回归&#xff0c;给诸位道友整点绝活。 交代下背景&#xff1a;要做一个行转列的汇总统计&#xff0c;而且&#xff0c;由于是行转列&#xff0c;列的数量不固定&#xff0c;所以&#xff0c;没法使用正常的SqlSugar框…

C++入门基础知识13

C 的关键字&#xff08;接上一篇博文&#xff09;&#xff01;&#xff01; 10. const_cast用法&#xff1a; 该运算符用来修改类型的 const 或 volatile 属性。除了 const 或 volatile 修饰之外&#xff0c; type_id 和 expression 的类型是一样的。常量指针被转化成非常量指针…

催收业务怎么提高接通率

提高催收呼叫业务的接通率是一个综合性的任务&#xff0c;需要从多个方面进行优化。以下是一些具体的策略和建议&#xff1a; 一、优化呼叫时间与频次 1. 选择合适的呼叫时间&#xff1a;通过分析目标客户的活跃时段&#xff0c;选择他们最可能接听电话的时间进行呼叫…

iOS Object-C 创建类别(Category) 与使用

有时候使用系统给出类或者第三方的类,但是呢它们自带的属性和方法又太少,不够我们的业务使用,这时候就需要给“系统的类或者第三方类”创建一个类别(Category),把自己的想添加的属性和方法写进来. Category模式用于向已经存在的类添加方法从而达到扩展已有类的目的 一:创建Ca…

零碳工厂:我国工业转型升级的绿色引擎

面对全球气候变化的严峻挑战&#xff0c;我国提出了碳达峰和碳中和的宏伟目标。零碳工厂作为工业领域实现这一目标的重要途径&#xff0c;正成为推动我国工业转型升级的绿色引擎。本文将提供一站式零碳工厂服务指南&#xff0c;帮助企业迈向零碳排放&#xff0c;共同构建绿色低…

零售企业做好人事管理并不难!智能化人事管理平台解决企业三大痛点!

在零售行业的激烈竞争中&#xff0c;优秀的人事管理策略是企业成功的关键。然而&#xff0c;传统的人事管理模式常常面临人员分散、流程繁琐、跨部门协作困难等挑战。为了应对这些问题&#xff0c;一站式人事管理平台的出现&#xff0c;为企业提供了数字化的解决方案。本文将探…

[论文泛读]zkLLM: Zero Knowledge Proofs for Large Language models

文章目录 介绍实验数据实验数据1实验数据2实验数据3 介绍 这篇文章发在CCS2024&#xff0c;CCS是密码学领域的顶会。作者是来自加拿大的University of Waterloo。文章对大语言模型像GPT和LLM等大语言模型实现了零知识可验证执行&#xff0c;但不涉及零知识可验证训练。个人觉得…

PAT--1124.最近的斐波那契数

题目描述 算法分析 找到把n夹在中间的两个斐波那契数列的数字&#xff0c;比如输入的数字9&#xff0c;你需要找到数字8和13&#xff0c;然后再输出差值绝对值较小的那个数字&#xff0c;如果一样&#xff0c;输出较小的数字 完整代码 #include<iostream> using names…

npm install pnpm -g 报错的解决方法

npm install pnpm -g 报错的解决方法 npm error code ETIMEDOUT npm error errno ETIMEDOUT npm error network request to https://registry.npmjs.org/pnpm failed, reason: npm error network This is a problem related to network connectivity. npm error network In mo…

三防加固平板电脑定制:现代工业环境的最佳选择

在数字化转型的浪潮中&#xff0c;工业领域正经历着前所未有的变革。智能设备的引入&#xff0c;尤其是定制化的三防加固平板电脑&#xff0c;正在成为现代工业环境中的关键工具。这些设备不仅能够承受严苛的工作条件&#xff0c;还能提供高度定制化的功能&#xff0c;以满足特…

萌啦数据使用体验,萌啦数据ozon体验如何

在跨境电商的浩瀚星海中&#xff0c;Ozon作为俄罗斯及独联体地区领先的电商平台&#xff0c;正以其独特的魅力和无限潜力吸引着全球卖家的目光。而在这片蓝海中航行&#xff0c;精准的数据分析与洞察无疑是每位船长手中的罗盘。今天&#xff0c;我们就来深入探讨一款备受好评的…

刷题技巧:双指针法的核心思想总结+例题整合+力扣接雨水双指针c++实现

双指针法的核心思想是通过同时操作两个指针来遍历数据结构&#xff0c;通常是数组或链表&#xff0c;以达到优化算法性能的目的。具体来说&#xff0c;双指针法能够减少时间复杂度、空间复杂度&#xff0c;或者简化逻辑结构。以下是双指针法的几个核心思想&#xff1a; ps 下面…

rem、em 和 px、inherit 加案例

一、rem、em 和 px 是三种常用的 CSS 长度单位&#xff0c;每种单位在不同的场景下有不同的应用和效果。以下是它们的区别&#xff1a; 以下是它们的区别&#xff1a; px (像素) 定义: px 是相对单位&#xff0c;表示屏幕上的一个物理像素点。它是一个固定的单位&#xff0c;…

手机电量消耗分析工具 Battery Historian 指南

阅读五分钟&#xff0c;每日十点&#xff0c;和您一起终身学习&#xff0c;这里是程序员Android 本篇文章主要介绍 Android 开发中 电量 的部分知识点&#xff0c;通过阅读本篇文章&#xff0c;您将收获以下内容: 一、安装Battery Historian二、收集Batterystats 数据三、使用B…

YOLO好像也没那么难?

“学YOLO的念头是想整个游戏外挂&#xff01;” 目录 基本原理 模型推理 IOU交并比 NMS非极大值抑制 模型训练 损失函数LOSS 代码实现 YOLO学习渠道 基本原理 模型推理 学习一个新的神经网络结构&#xff0c;作者认为整明白输入和输出是怎么回事就OK了&#xff0c;至于…

平安城市/雪亮工程现状及需求分析:EasyCVR视频汇聚平台助力雪亮工程项目建设

一、背景现状 经过近几年的努力&#xff0c;平安城市雪亮工程建设取得了显著的成绩&#xff0c;完成了前端高清视频点位和高清卡口系统建设&#xff0c;建成了&#xff08;视频监控类&#xff09;、&#xff08;卡口类&#xff09;和&#xff08;应用类&#xff09;的平台。这…

Linux笔记 --- 目录检索

基本概念 Linux中的目录与windows的文件夹相似但是概念大相径庭&#xff0c;windows中子文件一定不会比母文件夹大&#xff0c;但在Linux目录中是可以实现的&#xff0c;目录是一种文件索引表&#xff0c;下图是分区和目录的关系 Linux中目录是一组由文件名和索引号组成的索引表…