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;}@Overrideprotected 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");}}
}

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

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

相关文章

内网靶机~~dc-2

一、信息收集 1.端口扫描&#xff1a; nmap -sV -p 1-10000 10.1.1.4 2.CMS识别 3.目录扫描&#xff1a; dirsearch http://10.1.1.4/ 4.FLAG1 似乎让我们用cewl生成密码字典&#xff0c;并爆破登录。 cewl -w rewl_passwd.txt http://dc-2/index.php/flag/ 总结&#xff…

离线Linux/openEuler服务器指定本地yum仓库

1、前提准备一个预装坏境比较完整的linux镜像文件&#xff0c;本文服务器使用的是openEuler 官网&#xff1a;openEuler下载 | 欧拉系统ISO镜像 | openEuler社区官网 2、上传镜像文件至服务器 如果是集群服务器&#xff0c;上传其中一台服务器之后&#xff0c;使用scp指令将镜…

数据结构刷题篇 之 【力扣二叉树基础OJ】详细讲解(含每道题链接及递归图解)

有没有一起拼用银行卡的&#xff0c;取钱的时候我用&#xff0c;存钱的时候你用 1、相同的树 难度等级&#xff1a;⭐ 直达链接&#xff1a;相同的树 2、单值二叉树 难度等级&#xff1a;⭐ 直达链接&#xff1a;单值二叉树 3、对称二叉树 难度等级&#xff1a;⭐⭐ 直达…

【滑动窗口】Leetcode 最大连续1的个数 III

题目解析 1004. 最大连续1的个数 III 按照k的数值将0反转成1&#xff0c;记录数组中连续1的最长个数 算法讲解 我们需要一个变量temp记录翻转的次数&#xff0c;每遇到一次0&#xff0c;temp。当temp > k的时候此时说明翻转0已经到达极限&#xff0c;已经不可以在翻转了&…

基于二级片内硬件堆栈的后向CFI 验证方法研究,第三章

随着计算机技术的发展&#xff0c;针对计算机系统的恶意攻击越来越多&#xff0c;造成了巨大的经济损失。面向返回导向编程等恶意攻击方式通过修改堆栈中程序返回地址劫持控制流&#xff0c;达到恶意攻击的目的。后向控制流完整性即返回地址的完整性验证&#xff0c;是一种保护…

Tesla技术方案解析

Tesla技术方案解析 附赠自动驾驶学习资料和量产经验&#xff1a;链接 参考&部分摘选&#xff1a; EatElephant&#xff1a;解读: Tesla Autopilot技术架构 chenq100&#xff1a;TechTips - 031: “Tesla AI Day 2021”学习笔记 All you need to know about Tesla AI Da…

基于单片机的二维码LCD显示控制设计

**单片机设计介绍&#xff0c;基于单片机的二维码LCD显示控制设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的二维码LCD显示控制设计是一个集硬件、软件与通信于一体的综合性项目。此设计的主要目标是实现单片机…

蓝桥备赛——堆队列

AC code import os import sys import heapq a [] b [] n,k map(int,input().split())for _ in range(n):x,y map(int,input().split())a.append(x)b.append(y) q []# 第一种情况&#xff1a;不打第n个怪兽# 将前n-1个第一次所需能量加入堆 for i in range(n-1):heapq.h…

Doris实践——叮咚买菜基于OLAP引擎的应用实践

目录 前言 一、业务需求 二、选型与对比 三、架构体系 四、应用实践 4.1 实时数据分析 4.2 B端业务查询取数 4.3 标签系统 4.4 BI看板 4.5 OLAP多维分析 五、优化经验 六、总结 原文大佬介绍的这篇Doris数仓建设实践有借鉴意义的&#xff0c;这些摘抄下来用作沉淀学…

NFT Insider #125:Astar将与索尼开发的新公链将关注游戏或 NFT 等众多领域

引言&#xff1a;NFT Insider由NFT收藏组织WHALE Members &#xff08;https://twitter.com/WHALEMembers&#xff09;、BeepCrypto &#xff08;https://twitter.com/beep_crypto&#xff09;联合出品&#xff0c;浓缩每周NFT新闻&#xff0c;为大家带来关于NFT最全面、最新鲜…

TR2 - Transformer模型的复现

目录 理论知识模型结构结构分解黑盒两大模块块级结构编码器的组成解码器的组成 模型实现多头自注意力块前馈网络块位置编码编码器解码器组合模型最后附上引用部分 模型效果总结与心得体会 理论知识 Transformer是可以用于Seq2Seq任务的一种模型&#xff0c;和Seq2Seq不冲突。 …

STL —— vector(1)

博主首页&#xff1a; 有趣的中国人 专栏首页&#xff1a; C专栏 本篇文章主要讲解vector使用的相关内容 1. vector简介 vector 是 C 标准库中的一个容器类模板&#xff0c;它提供了动态数组的功能&#xff0c;可以方便地管理和操作元素的集合。下面是关于 vector 的一些基本信…

NRF24L01P和SI24R1的区别

NRF24L01无线模块广泛地运用于&#xff1a;无线门禁、无线数据通讯、安防系统、遥控装置、遥感 勘测、智能运动设备、工业传感器&#xff1b;平常我们用到的无线鼠标基本上采用的都是NORDIC的N RF24L01无线模块方案&#xff0c;而且&#xff0c;只需要一个5号电池即可。 几年前…

HarmonyOS实战开发-如何实现一个自定义抽奖圆形转盘

介绍 本篇Codelab是基于画布组件、显式动画&#xff0c;实现的一个自定义抽奖圆形转盘。包含如下功能&#xff1a; 通过画布组件Canvas&#xff0c;画出抽奖圆形转盘。通过显式动画启动抽奖功能。通过自定义弹窗弹出抽中的奖品。 相关概念 Stack组件&#xff1a;堆叠容器&am…

详解TCP的三次握手和四次挥手

文章目录 1. TCP报文的头部结构2. 三次握手的原理与过程三次握手连接建立过程解析 3. 四次挥手的原理与过程四次挥手连接关闭过程的解析 4. 常见面试题 深入理解TCP连接&#xff1a;三次握手和四次挥手 在网络通信中&#xff0c;TCP&#xff08;传输控制协议&#xff09;扮演着…

人才推荐 | 材料化学博士,热衷于创新且可扩展的电池技术开发

编辑 / 木子 审核 / 朝阳 伟骅英才 伟骅英才致力于以大数据、区块链、AI人工智能等前沿技术打造开放的人力资本生态&#xff0c;用科技解决职业领域问题&#xff0c;提升行业数字化服务水平&#xff0c;提供创新型的产业与人才一体化服务的人力资源解决方案和示范平台&#x…

java多线程——概述,创建方式及常用方法

前言&#xff1a; 学习到多线程了&#xff0c;整理下笔记&#xff0c;daydayup!!! 多线程 什么是线程 线程&#xff08;Thread&#xff09;是一个程序内部的一条执行流程。若程序只有一条执行流程&#xff0c;那这个程序就是单线程的程序。 什么是多线程 多线程是指从软硬件上…

【AIGC】如何在Windows/Linux上部署stable diffusion

文章目录 整体安装步骤windows10安装stable diffusion环境要求安装步骤注意事项参考博客其他事项安装显卡驱动安装cuda卸载cuda安装对应版本pytorch安装git上的python包Q&A linux安装stable diffusion安装anaconda安装cudagit 加速配置虚拟环境挂载oss&#xff08;optional…

传播力研究期刊投稿发表

《传播力研究》杂志是经国家新闻出版总署批准&#xff0c;黑龙江日报报业集团主管主办&#xff0c;面向全国公开发行的学术刊物。本刊为新闻、传媒、传播学类专业院校师生、文化传播理论研究者和从业人员及爱好者&#xff0c;开展学术交流与研讨&#xff0c;汲取当今业界新鲜的…

RGB,深度图,点云和体素的相互转换记录

目录 1.RGBD2Point 1.2 步骤 2.Point2Voxel-Voxelization 2.1 原理 2.2 代码 3.Voxel2Point 4.Point2RGB 5.Voxel2RGB 1.RGBD2Point input&#xff1a;RGB D 内外惨 output&#xff1a;points cloud def depth2pcd(depth_img):"""深度图转点云数据图…