ForkJoinPool、CAS原子操作

ForkJoinPool

ForkJoinPool是由JDK1.7后提供多线程并行执行任务的框架。可以理解为一种特殊的线程池。

1.任务分割:Fork(分岔),先把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割。

2.合并结果:join,分割后的子任务被多个线程执行后,再合并结果,得到最终的完整输出。

类似于分治的思想,把大任务一点点拆分为一个个小任务。

如果要统计1~100之间的和,当然可以直接暴力for循环,不过也可以把它拆分为10个任务,计算1到10的和,11到20的和…

  • ForkJoinTask:主要提供fork和join两个方法用于任务拆分与合并;一般用子类 RecursiveAction(无返回值的任务)和RecursiveTask(需要返回值)来实现compute方法。

public abstract class ForkJoinTask<V> implements Future<V>, Serializable

可以看到,ForkJoinTask实现了Future这个接口,也就是说,我们也可以通过ForkJoinTask来获取线程的状态、结果等。

  • ForkJoinPool:调度ForkJoinTask的线程池;

  • ForkJoinWorkerThread:Thread的子类,存放于线程池中的工作线程(Worker);

  • WorkQueue:任务队列,用于保存任务;
    ForkJoinPool forkJoinPool=new ForkJoinPool(8);//最多拆分为8个线程
    java.util.concurrent.ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(new ForkJoinTask(1, 100));
        System.out.println(forkJoinTask.get());

	static class ForkJoinTask extends RecursiveTask<Integer>{
        int start;
        int end;

        public ForkJoinTask(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected Integer compute() {
            if((end-start)<=10){
                int count=0;
                for(int i=start;i<=end;i++){
                    count+=i;
                }
                System.out.println("当前线程为:"+Thread.currentThread().getName());
                return count;
            }else{
                int mid=start+end>>1;
                ForkJoinTask subTask1=new ForkJoinTask(start,mid);
                subTask1.fork();
                ForkJoinTask subTask2=new ForkJoinTask(mid+1,end);
                subTask2.fork();
                return subTask1.join()+subTask2.join();
            }
        }
    }

计算1~100的和,如果end-start小于等于10就直接暴力进行加法运算,如果大于10,就继续拆分。

ForkJoinPool的设计思想

  • 普通线程池内部有两个重要集合:工作线程集合(普通线程),和任务队列。
  • ForkJoinPool也类似,线程集合里放的是特殊线程ForkJoinWorkerThread,任务队列里放的是特殊任务ForkJoinTask
  • 不同之处在于,普通线程池只有一个队列。而ForkJoinPool的工作线程ForkJoinWorkerThread每个线程内都绑定一个双端队列。
  • 在fork的时候,也就是任务拆分,将拆分的task会被当前线程放到自己的队列中。
  • 如果有任务,那么线程优先从自己的队列里取任务执行,以LIFO先进后出方式从队尾获取任务,
  • 当自己队列中执行完后,工作线程会跑到其他队列以work−stealing窃取,窃取方式为FIFO先进先出,减少竞争。

  1. 任务拆分:线程首先将大任务拆分成更小的任务。
  2. 本地队列:拆分出的小任务通常会被放置在执行这个任务的线程的本地队列中。这个队列是一个双端队列(deque)。
  3. 任务窃取:其他闲置的线程可以从这个队列的另一端窃取任务来执行。这意味着,虽然拆分出的任务最初是放在原线程的队列中,但其他线程可以参与处理这些任务。
  4. 负载均衡:通过这种方式,ForkJoinPool试图在其所有线程之间实现负载均衡,从而提高效率。

举例:

假设你有一个大任务:计算从1加到10000的总和。这个任务可以通过拆分成更小的任务来并行处理。

  1. 任务拆分:线程A开始执行这个任务,它决定将任务拆分成两个更小的任务:第一个是计算1到5000的总和,第二个是计算5001到10000的总和。
  2. 放置在本地队列:线程A将这两个任务放入它的本地队列。此时,它开始执行其中一个任务(比如计算1到5000的总和)。
  3. 工作窃取:此时,另一个线程B处于空闲状态,它会查看线程A的队列。线程B发现队列中有待处理的任务(计算5001到10000的总和),于是它将这个任务从队列中窃取并开始执行。
  4. 并行处理:线程A和线程B现在都在执行一个较小的任务。一旦各自的任务完成,结果会被汇总。在这个例子中,两个任务的结果将被加在一起以得到最终的总和。

注意点

使用ForkJoin将相同的计算任务通过多线程执行。但是在使用中需要注意:

  • 注意任务切分的粒度,也就是fork的界限。并非越小越好
  • 判断要不要使用ForkJoin。任务量不是太大的话,串行可能优于并行。因为多线程会涉及到上下文的切换

CAS(比较交换)原子操作

在说CAS之前先说一下什么是原子操作

原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为"不可被中断的一个或一系列操作" 。

CAS(Compare-and-Swap/Exchange),即比较并替换,是一种实现并发常用到的技术。CAS的整体架构如下:

  • 初始状态:计数器的值为0。
  • 线程A 读取计数器的值,得到0,打算将其增加到1。
  • 线程B 也读取计数器的值,得到0,同样打算将其增加到1。

此时,假设两个线程都尝试执行CAS操作来更新计数器的值。

理想的CAS操作:

  1. 线程A 的CAS操作先执行,它比较当前计数器的值(0)与预期值(0),发现匹配,因此成功将计数器的值更新为1。
  2. 接着,线程B 尝试执行它的CAS操作。这时,它比较当前计数器的值(现在为1)与其预期值(0),发现不匹配,因此不执行更新。

在这个情况下,计数器的最终值是1,这是正确的结果。每个线程都试图将计数器增加1,但只有一个线程成功了,因为CAS操作确保了计数器的每次更新都是基于最新的、有效的值。

如果CAS不按预期行为:

假设当线程B的预期值不匹配时,CAS操作仍然执行了更改,将计数器从1增加到2。

这将导致以下问题:

  • 数据不一致:这意味着两个线程的操作都基于同一个旧值(0),从而错误地假定计数器未被其他线程更改。
  • 结果错误:最终计数器的值变为2,而实际上只应该被增加1次。这是因为线程B没有正确地检测到线程A已经更新了计数器。
public class TestAtomic {
    public static void main(String[] args) {
        AtomicInteger atomicInteger=new AtomicInteger(1);
        atomicInteger.addAndGet(1);
        atomicInteger.incrementAndGet();
    }
}

以上两种方法都是给当前值+1,addAndGet(1)表示是在当前值的基础上+1,incrementAndGet表示自增。

源码剖析

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

offset:当前变量的地址=当前类的地址+偏移量offset

compareAndSwapInt(o, offset, v, v + delta):判断是否交换成功了,没有成功会去一直获取内存中value的值。

由此可以看出CAS是有一定弊端的,在面临高并发的场景下,可以持续死循环,导致CPU飙高。

CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

  1. 自旋(循环)时间长开销很大,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销,注意这里的自旋是在用户态/SDK 层面实现的。
  2. 只能保证一个共享变量的原子操作,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
  3. ABA问题,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比CAS更高效。

ABA问题:

加入A拿到Value,并且进行了修改,A=1(这时候B拿到了A=1,接着A继续执行) —> A=2 —> A=1,这时候B一看,好家伙,Value还是1,该到我改了吧,这下直接A=5了,这就会导致原子性被破坏了。

可以通过引入AtomicStampedReference来解决ABA的问题

  • AtomicStampedReference:原子更新带有版本号的引用类型。
public class TestAtomic {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

        int expectedReference = 1; // 当前期望的值
        int newReference = 2; // 新值
        int expectedStamp = 1; // 当前期望的版本号
        int newStamp = 2; // 新的版本号

        boolean wasUpdated = atomicStampedReference.compareAndSet(expectedReference, newReference, expectedStamp, newStamp);

        if (wasUpdated) {
            System.out.println("Update successful");
        } else {
            System.out.println("Update failed");
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C#手麻系统源码,医院手术麻醉信息系统源码,前端框架:Vue,Ant-Design,后端框架:百小僧开源框架

手术麻醉管理系统覆盖了从患者入院&#xff0c;经过术前、术中、术后&#xff0c;直至出院的全过程。医院手术麻醉系统能够规范麻醉科和手术室的工作流程、实现麻醉手术过程中的信息数字化和网络化、自动生成麻醉手术中的各种医疗文书、完整共享HIS、LIS和PACS等手术患者信息&a…

RPA机器人:人人都会实现的机器人

在这个数字化飞速发展的时代&#xff0c;微信已经成为我们日常生活和工作中不可或缺的社交工具。然而&#xff0c;随着联系人数量的不断增加&#xff0c;如何高效管理这些社交关系成为了许多人面临的挑战。今天&#xff0c;我要为大家介绍的&#xff0c;是一款能够彻底改变你微…

PHP实现单列内容快速查重与去重

应用场景:excel一列内容比如身份证号&#xff0c;可能有重复的&#xff0c; 则用此工具快速查询那些重复及显示去重后内容。 使用&#xff1a;粘贴一列数据&#xff0c;然后提交发送。 <?php $tm "单列查重去重(粘贴Excel中1列内容查重)!";function tipx($str…

WEB embedded APP (javafx)

WEB embedded APP &#xff08;javafx&#xff09; &#xff08;BS 嵌入CS&#xff09; CS嵌入BS_哔哩哔哩_bilibili

生信软件14 - bcftools提取和注释VCF文件关键信息

bcftools可用于变异信息的描述性统计&#xff0c;计算&#xff0c;过滤和格式转换。 1. 显示VCF文件的头信息 bcftools view -h sample.vcf##fileformatVCFv4.2 ##FILTER<IDPASS,Description"All filters passed"> ##bcftoolsVersion1.5htslib-1.5 ##bcftool…

vmware,linux,centos7,NAT模式下的网络配置

centos7的NAT网络配置 NAT模式说明虚拟机网络配置工具本机配置net8网络&#xff08;NAT的网域&#xff09;本机的IP配置(用于net8局域网内解析主机IP和域名对应关系使用)&#xff08;可选&#xff09;虚拟机内的网络配置虚拟机ping不通www.baidu.com的情况下虚拟机ping可以ping…

我劝你不要买29.99万的小米SU7

文 | AUTO芯球 作者 | 雷歌 我在想我是不是贱啊&#xff1f;&#xff01; 我昨晚兴奋得头晕脸热的&#xff0c;身边一众关注车的朋友&#xff0c;也感觉到了车圈过年的气氛。 原因就是小米SU7的价格公布了。 21.59万元起售价格出来以后&#xff0c;就好比新年0点一过的那个…

C++:sizeof关键字(7)

sizeof用于统计数据所占用内存的大小 用法&#xff1a;sizeof( 变量名称 / 变量) 直接上代码&#xff0c;可以在让大家直观的感受到sizeof关键字的用法 #include<iostream> using namespace std;// 语法&#xff1a; sizeof&#xff08;数据类型|变量名&#xff09;// 用…

PS从入门到精通视频各类教程整理全集,包含素材、作业等(2)

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 初级教程素材 等文件 https://www.alipan.com/s/fC…

从0到1利用express搭建后端服务

目录 1 架构的选择2 环境搭建3 安装express4 创建启动文件5 express的核心功能6 加入日志记录功能7 日志记录的好处本节代码总结 不知不觉学习低代码已经进入第四个年头了&#xff0c;既然低代码很好&#xff0c;为什么突然又自己架构起后端了呢&#xff1f;我有一句话叫低代码…

C++——vector类及其模拟实现

前言&#xff1a;前边我们进行的string类的方法及其模拟实现的讲解。这篇文章将继续进行C的另一个常用类——vector。 一.什么是vector vector和string一样&#xff0c;隶属于C中STL标准模板库中的一个自定义数据类型&#xff0c;实际上就是线性表。两者之间有着很多相似&…

安装docker 并搭建出一颗爱心树

1、docker介绍 Docker 是⼀个开源的容器运⾏时软件&#xff08;容器运⾏时是负责运⾏容器的软件&#xff09;&#xff0c;基于 Go 语 ⾔编写&#xff0c;并遵从 Apache2.0 协议开源。 Docker可以让开发者打包⾃⼰的应⽤以及依赖到⼀个轻量的容器中&#xff0c;然后发布到任何…

Python 垃圾回收和弱引用(Weakref)

Python中的赋值语句是建立变量名与对象的引用关系&#xff0c;多个变量可以引用同一个对象&#xff0c;当对象的引用数归零时&#xff0c;可能会被当作垃圾回收。而弱引用即可以引用对象&#xff0c;又不会阻止对象被当作垃圾回收&#xff0c;因此这个特性非常适合用在缓存场景…

值得收藏!2024年人工智能顶级会议投稿信息汇总(计算机视觉领域)

计算机视觉是人工智能领域的重要分支。它融合了图像处理、模式识别、机器学习和人工智能等多个领域的技术&#xff0c;旨在让计算机具备类似甚至超越人类视觉系统的能力。本文将精选介绍计算机视觉领域内的重要会议&#xff0c;包括会议主题、稿件提交的截止日期、会议的时间与…

SpringCloudConfig 使用git搭建配置中心

一 SpringCloudConfig 配置搭建步骤 1.引入 依赖pom文件 引入 spring-cloud-config-server 是因为已经配置了注册中心 <dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-config-server</…

【软件安装】(十四)Ubuntu22.04安装Psensor硬件监视器

一个愿意伫立在巨人肩膀上的农民...... Ubuntu系统硬件运行查询输入指令太繁琐&#xff0c;终端展示不直观&#xff0c;因此这款具有可视化监控Ubuntu系统下当前电脑的硬件CPU&#xff08;中央处理器&#xff09;、GPU&#xff08;显卡&#xff09;和硬盘等温度等功能&#xff…

2024年妈妈杯数学建模思路B题思路分享

文章目录 1 赛题思路2 比赛日期和时间3 组织机构4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

MySQL进阶——锁

锁 概述 全局锁 表级锁 行级锁 概述 同Java中的锁。目的是为了保证数据一致性、完整性&#xff0c;提高并发安全、控制访问顺序。 分类 在MySQL中&#xff0c;根据锁的粒度分&#xff0c;分为以下3种&#xff1a; 全局锁&#xff1a;锁定数据库种的所有表 表级锁&#…

『大模型笔记』提示工程、微调和RAG之间对比

提示工程、微调和RAG之间对比 文章目录 一. 提示工程、微调和RAG之间对比二. 参考文章文章:Prompt Engineering vs Finetuning vs RAG一. 提示工程、微调和RAG之间对比 Prompt EngineeringFinetuning

截图识别对比:CnOCR与PaddleOCR

1、需求 想使用PyAutoGUI做界面自动化&#xff0c;需要一个ocr库识别压测软件的文字&#xff0c;然后获取定位。现在找到了CnOCR与PaddleOCR&#xff0c;都安装来试试看&#xff0c;哪一个更适合我的需求&#xff0c;这里对这俩库进行对比。 本机环境&#xff1a; win11python…