【Java EE初阶十】多线程进阶二(CAS等)
1. 关于CAS
CAS: 全称Compare and swap,字面意思:”比较并交换“,且比较交换的是寄存器和内存;
一个 CAS 涉及到以下操作:
下面通过语法来进一步进项说明:
下面有一个内存M,和两个寄存器A,B;
CAS(M,A,B):该条指令意味着如果M和A中的值相同,则将M 和B中的值进行交换,在完成上述操作之后,返回true;如果M和A中的值不相同,则不用发生任何交换,同时返回false;
综上所述,交换的本质就是当寄存器和内存中的值一样时,将其他寄存器中不同与内存中的值赋给内存;
1.1 CAS伪代码
下面写的代码是伪代码,该段代码不能被顺利的编译运行,但是可以用来辅助理解上述所说 CAS 的工作流程.
boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}
CAS其实是一个cpu指令(一条cpu指令就能满足上述比较交换的逻辑),说明单个cpu指令是原子的。故此可以使用CAS完成一些操作(给编写线程安全的代码,引入了新的思路并且不涉及线程阻塞),进一步代替“加锁”;
基于CAS实现线程安全的方式,也称为“无锁编程”,其优缺点如下:
优点:保证线程安全,同时避免阻塞;
缺点:
1、代码会更加复杂,不好理解;
2、只能够适合一些特定的场景,不如加锁方式更加普遍;
Cas本质上是cpu提供的指令->又被操作系统封装提供成api->又被jvm封装,也被提供成api->被程序员使用了;
1.2 CAS 有哪些应用
1.2.1 实现原子类
Int++操作不是原子的(load,add,save),其中AtomicInteger,基于CAS的方式对int进行封装了,此时进行int++(基于cas指令来实现的)就是原子的操作了
结论:原子类里面是基于cas来实现的;下面是简化的代码:
class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;while ( CAS(value, oldValue, oldValue+1) != true) {oldValue = value;}return oldValue;}
}
通过在多线程t1和t2的分析中了解cas的简单原理:
我们所说的“线程不安全”本质上是进行自增的过程中,被其他线程的自增行为穿插执行了;但是CAS是让这里的自增不要被穿插执行,其核心思路类似于加锁,但是加锁是通过阻塞的方式避免穿插,CAS则是通过重试的方式避免被穿插;
1.2.2 实现自旋锁
1.2.2 关于ABA问题
CAS进行操作的关键,是通过值“有没有发生变化”作为“有没有其他线程穿插执行的”判定依据,但是在一些的极端的情况下,我们的值本来是正常情况下的A的成为A->B->A,针对第一个要判断的线程来说,看起来由于值没有变二判定没有其他线程进行穿插执行,但是事实上我们已经存在线程穿插执行的问题了。
如下图所示,虽然使用cas语句进行判定的时候内存中和寄存器中的数值一样,但是我们不能确定内存中的值是始终没有发生变化还是发生变化之后被其他线程又成功改回来了;
2. JUC的相关类
JUC(java.util.concurrent),且Concurrent:并发的意思,这个包里面的内容,主要就是一些多线程相关的组件;
2.1 Callable 接口
该接口也是一种创建线程的方式,适合于想让某个线程执行一个逻辑,并且返回结果的时候;相对而言,runnable不关注结果,代码举例如下:
代码示例: 创建线程计算 1 + 2 + 3 + ... + 1000, 使用 Callable 版本
创建一个匿名内部类, 实现 Callable 接口. Callable 带有泛型参数. 泛型参数表示返回值的类型.
重写 Callable 的 call 方法, 完成累加的过程. 直接通过返回值返回计算结果.
把 callable 实例使用 FutureTask 包装一下.
创建线程, 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的 call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中.
在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.
Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}return sum;} }; FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread t = new Thread(futureTask); t.start(); int result = futureTask.get(); System.out.println(result) }
下面图解主要是关于futuretask的讲解:
理解 Callable:
Callable 和 Runnable 相对, 都是描述一个 "任务". Callable 描述的是带有返回值的任务, Runnable 描述的是不带返回值的任务. Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为 Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定. FutureTask 就可以负责这个等待结果出来的工作
2.2 ReentrantLock
可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全. ReentrantLock 也是可重入锁. "Reentrant" 这个单词的原意就是 "可重入";
ReentrantLock 的用法:
lock(): 加锁, 如果获取不到锁就死等.
trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.
unlock(): 解锁
ReentrantLock lock = new ReentrantLock();
-----------------------------------------
lock.lock();
try { // working
} finally { lock.unlock()
}
ReentrantLock相对于synchronized的优势:
1、ReentrantLock,在加锁的时候,有两种方式lock(加锁失败就会阻塞等待) 和trylock(加锁失败就会放弃);
2、ReentrantLock还通过了公平锁的实现(默认情况下是非公平锁)
3、ReentrantLock提供了更强大的等待通知机制,主要是搭配了condition类,实现等待通知的;
总的来说,我们在加锁的时候,首选synchronized(会有优化锁的策略),因为ReentrantLock使用起来更加复杂,尤其是容易忘记解锁;
2.3 信号量 Semaphore
Semaphore 信号量, 本质上就是一个计数器. 用来表示 "可用资源的个数".每次申请一个可用资源,就需要让计数器-1(p操作);每次释放一个可用资源,就需要让计数器+1(v操作),操作系统,提供了信号量实现,同时操作系统也提供了api;jvm封装了这样的api,就可以在java代码中使用了;
理解信号量:
可以把信号量想象成是停车场的展示牌: 当前有车位 100 个. 表示有 100 个可用资源. 当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作) 当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作) 如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.
Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.
关于semaphore的代码如下:
Semaphore semaphore = new Semaphore(4);
Runnable runnable = new Runnable() {@Overridepublic void run() {try {System.out.println("申请资源");semaphore.acquire();System.out.println("我获取到资源了");Thread.sleep(1000);System.out.println("我释放资源了");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}
};
for (int i = 0; i < 20; i++) {Thread t = new Thread(runnable);t.start();
}
2.4 CountDownLatch
同时等待 N 个任务执行结束.主要适用于,多个线程来完成一系列任务的时候,用来衡量任务的进度是否完成。
比如需要把一个很大的任务,拆分成多个小任务,让这些小任务并发的去执行。就可以使用CountDownLatch来判定说当前的这些任务是否都完全全部完成了;
Eg:下载一个文件,就可以使用多线程下载;相比之下,有一些专业的下载工具(往往和资源服务器之间只有一个连接,服务器往往会对于连接传输的速度有一定的限制),就可成倍的提升下载速度(IDM),多线程下载(每个线程都建立一个连接,此时就需要把整个大任务进行分割)
CountDownLatch 主要有两个方法:
- await,该方法调用的时候就会阻塞,就会等待其他线程完成任务,当所有的线程都完全的完成了任务之后,此时这个await才会返回,才会继续往下走;
- CountDown,告诉CountDownLatch,我当前的一个子任务已经完成了
结果如下:
3 线程安全的集合类
3.1 多线程环境使用 ArrayList:
3.1.1 Collections.synchronizedList(new ArrayList);
synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List. synchronizedList 的关键操作上都带有 synchronized
3.1.2 使用 CopyOnWriteArrayList
写时拷贝;
比如,两个线程使用同一个arraylist,可能会读,也可能会修改;
如果要是两个线程读,则可以直接进行读操作即可;
如果某个线程需要进行修改,就把arraylist临时复制出一个副本,进行修改的线程就修改这个副本,与此同时,另外一个线程任然可以从原来的数据文件上读取数据,一旦这边修改的临时文件修改完毕,就会使用修改好的这份数据文件来代替原来的数据文件。
该方法的局限性:
- 当前操作的ArrayList不能太大(拷贝成本,不能太高)
- 更适用于一个线程去修改,而不能是多个线程去同时修改(多个线程读,一个线程修改)
这种场景特别适用于服务器的配置更新~~,可以通过配置文件来描述配置的详细内容(本身就不会很大),配置的内容会被读取到内存中,再有其他的线程读取这里的内容,但是修改这个配置内容,往往只能有一个线程来修改;
应用场景:使用某个命令让服务器重新加载配置,就可以使用写时拷贝的方式;
3.2 多线程环境使用哈希表
3.2.1 Hashtable
HashMap 本身不是线程安全的. 在多线程环境下使用哈希表可以使用: ConcurrentHashMap
Hashtable保证线程安全,主要是给关键方法加上synchronized(类似于给this加锁),同时只有两个线程在操作同一个Hashtable就会出现锁冲突
如上图所示,当两个不同的key映射到同一个数组下标上,就会出现hash冲突,使用链表来解决hash冲突;
按照上述这样的方式来操作 ,并且在不考虑触发扩容的前提下,操作不同的链表的时候就是线程安全的,相比之下,如果两个线程操作的是同一个链表,才会比较容易发生线程安全的问题;故此连个线程,操作的是不同的链表,就根本不用加锁,只要在操作同一个链表的时候才需要进行加锁;
3.2.2 ConcurrentHashMap
ConcurrentHashMap相比于 Hashtable 做出了一系列的改进和优化,简单如下所示:
1、 ConcurrentHashMap最核心的改进,就是把一个全局的大锁,改进成了每个链表独立的一把小锁,这样就大幅度的降低了锁冲突的概率(一个hash表有很多这样的链表,两个线程恰好同时访问一个链表的概率比较少)--->就是把每一个链表的头结点作为锁对象,synchronized可以使用任何对象作为锁对象
2、充分利用了cas的特性,把一些不必要加锁的环节给省略了,比如需要使用变量记录hash表中的元素个数,就可以使用原子操作(cas)修改元素个数;
3、 ConcurrentHashMap,还有一个激进的操作,针对读操作没有进行加锁,读和读之间,读和写之间,都不会有锁竞争;写和写之间是需要进行加锁的
q:是否会存在“读到一个修改了一半的数值呢”这种情况?
a:ConcurrentHashMap 在底层编码的过程中,比较谨慎的处理了一些细节,修改数值的时候就会避免使用++,--这种非原子的操作,使用=进行修改的时候,本身就是原子的,读的时候,要么读到的就是之前所写的旧的数值,要么读到的就是重写修改后的数值,不会出现一个修改到一半的数值;
4、ConcurrentHashMap针对扩容操作进行了单独的优化
本身Hashtable和HashMap在扩容的时候,都是需要把所有单独的元素都拷贝一遍的(如果元素较多的话,就会比较耗时)即1000个用户访问,且只有一个人在访问的时候触发扩容遇到卡顿,所以就需要化整为零的进行复制,一旦需要扩容,我们旧分为很多次进行搬运复制,每次只用复制一小部分防治这一个单次访问遇到卡顿;
当然,ConcurrentHashMap基本的使用方法和普通的HasMap完全一样
ps:本篇的内容到这里就结束啦,如果对你有所帮助的话,就请一键三连哦哦!!!
相关文章:

【Java EE初阶十】多线程进阶二(CAS等)
1. 关于CAS CAS: 全称Compare and swap,字面意思:”比较并交换“,且比较交换的是寄存器和内存; 一个 CAS 涉及到以下操作: 下面通过语法来进一步进项说明: 下面有一个内存M,和两个寄存器A,B; CAS(M,A,B)&am…...

与AI对话:编写高效Prompt的指南
与AI对话:编写高效Prompt的指南 一、明确目标 引导AI提供特定格式或内容答案的策略一、明确需求二、使用示例三、设置参数四、分步询问五、使用关键词 利用关键词引导AI重点关注核心内容的技巧一、确定关键概念二、使用专业术语三、强调重要性四、避免相关术语的混淆…...

QML用ListView实现带section的GridView
QML自带的GridView只能定义delegate,没有section,类似手机相册带时间分组标签的样式就没法做。最简单的方式就是组合ListViewGridView,或者ListViewFlow,但是嵌套View时,子级View一般是完全展开的,只显示该…...

docker之程序镜像的制作
目录 一、每种资源的预安装(基础) 安装 nginx安装 redis 二、dockerfile文件制作(基础) 打包 redis 镜像 创建镜像制作空间制作dockerfile 打包 nginx 镜像 三、创建组合镜像(方式一) 生成centos容器并…...

Git - 每次 git pull/push 时需要账号和密码解决方案
问题描述 在提交项目代码或者拉取代码的时候,每次 git 都要输入用户名密码,很烦~ 解决方案 让服务器记下来用户名和密码,此时输入一次,以后再 git push /pull 的时候就不用再输账号和密码了 # 配置 git 记录用户名和密码 git c…...

C语言中在main函数之后运行的函数
在 Linux 平台上,atexit 函数同样是一个用于注册终止处理函数的库函数,它是 C 标准库 <stdlib.h> 的一部分。atexit 函数允许你注册一个或多个函数,这些函数会在 main 函数执行结束后,或者在调用 exit 函数时,由…...
pytorch训练指标记录之tensoboard,wandb
详解Tensorboard及使用教程_tensorboard怎么用-CSDN博客文章浏览阅读5.1w次,点赞109次,收藏456次。目录一、什么是Tensorboard二、配置Tensorboard环境要求安装三、Tensorboard的使用使用各种add方法记录数据单条曲线(scalar)多条曲线(scalars)直方图(hi…...

C语言——oj刷题——实现字符串逆序
当我们需要逆序一个字符串的内容时,可以通过C语言编写一个函数来实现。下面将详细介绍如何通过C语言实现这个功能,并附上代码示例。 1、实现原理 要逆序一个字符串的内容,可以使用两个指针来交换字符串中对应位置的字符。具体实现原理如下&am…...

空气质量预测 | Matlab实现基于SVR支持向量机回归的空气质量预测模型
文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 政府机构使用空气质量指数 (AQI) 向公众传达当前空气污染程度或预测空气污染程度。 随着 AQI 的上升,公共卫生风险也会增加。 不同国家有自己的空气质量指数,对应不同国家的空气质量标准。 基于支持向量机(Su…...

Vue中的请求拦截器
目录 1 前言 2 使用方法 2.1 创建拦截器 2.2 引入拦截器 1 前言 我们常常会使用JWT令牌来验证登录,因此很多请求都需要携带JWT令牌,我们当然可以用{headers:{Authorization:xx}}的方式,向每个请求中都以这样的方式添加JWT令牌。不过这样…...

Java奠基】对象数组练习
目录 商品对象信息获取 商品对象信息输入 商品对象信息计算 商品对象信息统计 学生数据管理实现 商品对象信息获取 题目要求是这样的: 定义数组存储3个商品对象。 商品的属性:商品的id,名字,价格,库存。 创建三个…...

排序算法---快速排序
原创不易,转载请注明出处。欢迎点赞收藏~ 快速排序是一种常用的排序算法,采用分治的策略来进行排序。它的基本思想是选取一个元素作为基准(通常是数组中的第一个元素),然后将数组分割成两部分,其中一部分的…...

算法||实现典型数据结构的查找、添加和删除数据 并分析其时间和空间复杂度
实现典型数据结构的查找、添加和删除数据 并分析其时间和空间复杂度 线性结构: 数组:是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据。 查找数据 :随机访问 流程图 /** 查询元素下标…...

【蓝桥杯冲冲冲】Invasion of the Milkweed G
【蓝桥杯冲冲冲】Invasion of the Milkweed G 蓝桥杯备赛 | 洛谷做题打卡day30 文章目录 蓝桥杯备赛 | 洛谷做题打卡day30[USACO09OCT] Invasion of the Milkweed G题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 题解代码我的一些话 [USACO09OCT] Invasion of the Mi…...

【JAVA WEB】 百度热榜实现 新闻页面 Chrome 调试工具
目录 百度热榜 新闻页面 Chrome 调试工具 --查看css属性 打开调试工具的方式 标签页含义 百度热榜 实现效果: 实现代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"vi…...

Linux——动静态库
基础知识:动vs静 类型动静加载时机运行时编译时可复用性多个文件只需要加载一份库文件每个文件都需要加载一份文件性能链接次数越多越有优势链接次数越少越有优势 代码编写 静态库 生成静态库 libmath.a:add.o sub.oar -rc $ $^%.o:%.cgcc -c $<使用静态库 头文件和工…...

Vulnhub靶机:hacksudo-search
一、介绍 运行环境:Virtualbox 攻击机:kali(10.0.2.15) 靶机:hacksudo-search(10.0.2.50) 目标:获取靶机root权限和flag 靶机下载地址:https://download.vulnhub.co…...

Leetcode 188 买卖股票的最佳时机 IV
题意理解: 给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。 注意…...

win32编程系统BUG(Win32 API中的WM_SETTEXT消息)
由于频繁使用Win32 API中的WM_SETTEXT消息,导致内存占用直线上升。 暂未找到有效解决方案。...

Linux防火墙开放
记录一次问题 写的网络服务无法通信 代码没问题,IP绑定、端口绑定没问题,就是无法进行通信,这里要分2步走。 服务器控制台开放 进入防火墙 添加规则,这里以开放udp的8899端口为例 这里在服务器后台就已经开放了,但此时…...

通过 docker-compose 部署 Flink
概要 通过 docker-compose 以 Session Mode 部署 flink 前置依赖 Docker、docker-composeflink 客户端docker-compose.yml version: "2.2" services:jobmanager:image: flink:1.17.2ports:- "8081:8081"command: jobmanagervolumes:- ${PWD}/checkpoin…...

HarmonyOS ArkTS修改App的默认加载的界面(二十)
前言:在Android开发中想要修改默认启动页,只需要在AndroidManifest.xml中设置即可 只需要在启动的activity种添加如下属性即可 <intent-filter><action android:name"android.intent.action.MAIN" /><category android:name&qu…...

【前端高频面试题--Vue基础篇】
🚀 作者 :“码上有前” 🚀 文章简介 :前端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬前端高频面试题--Vue基础篇 Vue基本原理双向绑定与MVVM模型Vue的优点计算属性与监听属性计算属性监…...

Spring Boot 实现热插拔 AOP
现在有这么一个需求:就是我们日志的开与关是交给使用人员来控制的,而不是由我们开发人员固定写死的。大家都知道可以用aop来实现日志管理,但是如何动态的来实现日志管理呢?aop源码中的实现逻辑中有这么一个步骤,就是会依次扫描Advice的实现类,然后执行。我们要做的就是自…...

2月05日,每日信息差
第一、全球首套5G及6G天地一体网络低轨试验卫星发射入轨、。据了解,“中国移动01星”是全球首颗可验证5G天地一体演进技术的试验卫星,它搭载的基站可以利用卫星的广覆盖优势把5G信号传送到地面网络无法覆盖到的地方;另外一颗“‘星核’验证星…...

使用Python进行数据的描述性分析,用少量的描述性指标来概括大量的原始数据
在进行数据分析时,当研究者得到的数据量很小时,可以通过直接观察原始数据来获得所有的信息。但是,当得到的数据量很大时,就必须借助各种描述性指标来完成对数据的描述工作。用少量的描述性指标来概括大量的原始数据,对…...

【JS逆向三】逆向某某网站的sign参数,并模拟生成仅供学习
逆向日期:2024.02.06 使用工具:Node.js 类型:webpack 文章全程已做去敏处理!!! 【需要做的可联系我】 可使用AES进行解密处理(直接解密即可):AES加解密工具 1、打开某某…...

移动光猫gs3101超级密码及改桥接模式教程
文章目录 超级管理员账号改桥接模式路由器连接光猫,PPPOE拨号即可!附录:如果需要改桥接的话不知道拨号密码咋办打开光猫Telnet功能Telnet 登录 参考文章 移动光猫吉比特GS3101超级账号获取更改桥接 移动光猫gs3101超级密码及改桥接模式教程 …...

leetcode 153
153 寻找旋转排序数组中的最小值 这道题,如果我们熟悉数组 api,可以直接用 Arrays.sort()秒杀,这个方法使用了双轴快速排序算法。 解法1如下: class Solution {public int findMin(int[] nums) {Arrays.sort(nums);return nums…...

【MySQL】数据库的基础——数据库的介绍、MySQL的介绍和架构、SQL分类、MySQL的基本使用、MySQL的存储引擎
文章目录 MySQL1. 数据库的介绍1.2 主流数据库 2. MySQL的介绍2.1 MySQL架构2.2 SQL分类2.3 MySQL的基本使用2.4 MySQL存储引擎 MySQL 1. 数据库的介绍 数据库(Database,简称DB)是按照数据结构来组织、存储和管理数据的仓库。它是长期存储在计…...