【JavaEE】【多线程】进阶知识

目录

  • 一、常见的锁策略
    • 1.1 悲观锁 vs 乐观锁
    • 1.2 重量级锁 vs 轻量级锁
    • 1.3 挂起等待锁 vs 自旋锁
    • 1.4 普通互斥锁 vs 读写锁
    • 1.5 可重入锁 vs 不可重入锁
    • 1.6 不公平锁 vs 公平锁
  • 二、synchronized特性
    • 2.1 synchronized的锁策略
    • 2.2 synchronized加锁过程
    • 2.3 其它优化措施
  • 三、CAS
    • 3.1 CAS概念
    • 3.2 CAS应用场景
      • 3.2.1 实现原子类。
      • 3.2.2 实现自旋锁
    • 3.3 CAS的ABA问题
      • 3.3.1 ABA问题简介
      • 3.3.2 解决方案
  • 四、JUC组件
    • 4.1 Callable接口
    • 4.2 ReentrantLock类
    • 4.3 Semaphore类
    • 4.4 CountDownLatch类

一、常见的锁策略

1.1 悲观锁 vs 乐观锁

悲观和乐观是指锁竞争的激烈情况。

  1. 悲观锁:加锁的时候预测接下来的锁竞争会非常激烈,就需要针对这样的激烈情况额外做工作;
  2. 乐观锁:加锁的时候预测接下来的锁竞争不激烈,就不需要额外做工作去处理锁竞争情况;

1.2 重量级锁 vs 轻量级锁

这两种锁就对应上诉的悲观乐观情况下的处理机制。

  1. 重量级锁:应对上面的锁竞争激烈的悲观情况,效率更低;
  2. 轻量级锁:应对上面的锁竞争不激烈的情况,效率更高。

1.3 挂起等待锁 vs 自旋锁

这又是对上面的重量级与轻量级锁的典型实现。

  1. 挂起等待锁:重量级锁的典型实现,是操作系统内核级别的,加锁发生竞争,线程进入阻塞后,就需要内核进行唤醒。获取锁的周期会变长,但是这期间不会消耗CPU资源。
  2. 自旋锁:轻量级锁的典型实现,是应用程序级别的,加锁时发生竞争,一般不进行阻塞,而是通过忙等,等待后续程序唤醒。获取锁的周期很短,可以及时获取到锁,但是这期间会一直消耗CPU资源。

1.4 普通互斥锁 vs 读写锁

这两种锁是针对加锁解锁时的线程安全问题。

  • 普通互斥锁只有加锁,解锁操作,并且读操作不会出现线程安全问题;
  • 而读写锁有读加锁,写加锁,和解锁操作,
    读锁与读锁之间不互斥;
    读锁与写锁之间存在互斥;
    写锁与写锁之间也存在互斥。
    读写锁主要是针对读操作多,写操作少的情况服务。

1.5 可重入锁 vs 不可重入锁

这组锁就是针对同一个线程多次嵌套获取同一把锁。

  • 可重入锁:字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。
  • 不可重入锁:字面意思是“不可以重新进入的锁”,即同一个线程多次获取同一把锁,会报错。

1.6 不公平锁 vs 公平锁

这组锁是针对线程获取锁的概率设置的。

  • 不公平锁:在Java中不公平锁是概率均等的随机分配锁;
  • 公平锁:在Java中公平锁是按照等待的时间先来后到分配锁。因为操作系统是随机调度,为了实现公平锁就需要数据结构来记录先后调度顺序。

二、synchronized特性

2.1 synchronized的锁策略

锁策略如下:

  • synchronized是一个自适应的锁,开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁。
  • 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁。
  • 是一种普通互斥锁。
  • 是一种不公平锁。
  • 是一种可重入锁。

2.2 synchronized加锁过程

synchronized的自适应过程称为锁升级:无锁 -> 偏向锁 -> 自旋锁 -> 重量级锁。

  • 偏向锁:偏向锁不是真的 “加锁”, 只是给对象头中做一个 “偏向锁的标记”, 记录这个锁属于哪个线程.
    如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销)
    如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别
    当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态.
    就像跟你搞暧昧,但是不正式跟你确认关系,如果没人来追求你就一直不确定关系,如果有人追求你就确认关系。

  • 无锁 -> 偏向锁 :这个阶段是进入synchronized代码块;

  • 偏向锁 -> 自旋锁:这个阶段是拿到偏向锁后,遇到其他线程来竞争这个锁;

  • 自旋锁 -> 重量级锁:这个阶段是JVM发现当前的锁竞争非常激烈。

2.3 其它优化措施

除了上面的synchronized锁升级以外,还有以下的优化措施:

  • 锁消除:编译器优化措施,编译器会对加锁的代码进行判断(但这种判断是非常保守的,只有100%确定当前是单线程),如果当前逻辑不需要加锁,编译器就会自动去除synchronized。

  • 锁粗化:一个代码对细粒度的代码反复加锁解锁,就会将这个步骤优化为更粗粒度的加锁解锁。

  • 锁的粒度:加锁和解锁之间,包含的代码执行的逻辑/时间越多,则锁的粒度就越粗,反之越细。

三、CAS

3.1 CAS概念

CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功。

伪代码表示如下:

boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

3.2 CAS应用场景

3.2.1 实现原子类。

原子类:特指java.util.concurrent.atomic 包下的类,这些类中的操作都是原子的。

像我们前面Thread类详解,这篇文章介绍的非原子的操作带来的线程安全问题,如果使用原子类就不存在了。
例如执行下面这段代码:结果就一直是100000。


import java.util.concurrent.atomic.AtomicInteger;public class Demo {private static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndAdd(1);}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndAdd(1);}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(count.get());}
}

原子类线程安全的原理:

假设两个线程同时调用 getAndIncrement :

  1. 两个线程都读取 value 的值到 oldValue 中。 (oldValue 是一个局部变量, 在栈上. 每个线程有自己的栈)
  2. 线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值。
  3. 线程2 再执行 CAS 操作, 第一次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值。因此需要进入循环。在循环里重新读取 value 的值赋给 oldValue。
  4. 线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作。
  5. 线程1 和 线程2 返回各自的 oldValue 的值即可。

3.2.2 实现自旋锁

基于 CAS 实现更灵活的自旋锁, 获取到更多的控制权。

主要逻辑如下:

  • 通过 CAS 看当前锁是否被某个线程持有。
  • 如果这个锁已经被别的线程持有, 那么就自旋等待。
  • 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.

伪代码描述如下:

public class SpinLock {private Thread owner = null;public void lock(){while(!CAS(this.owner, null, Thread.currentThread())){}}public void unlock (){this.owner = null;}
}

3.3 CAS的ABA问题

3.3.1 ABA问题简介

假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A。
接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要先读取 num 的值, 记录到 oldNum 变量中.。
使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z。
但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A。
线程 t1 的 CAS 是期望 num 不变就修改. 但是 num 的值已经被 t2 给改了. 只不过又改成 A 了. 这
个时候 t1 究竟是否要更新 num 的值为 Z 。就不符合预期出现bug。

过程图:

bug例子:

假如你去取钱有1000,取500,不小心按了两次取款,我们只想扣一次500,第一次按相当于T2,第二次按相当于T1,先执行T2读取操作修改为500,这时又正好有人给你汇款了500,内存中与寄存器中又相等了,T1扣款的操作也会执行成功。

造成ABA问题的原因就是内存值既有加又有减,所以我们的解决ABA的方式如下。

3.3.2 解决方案

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期。
CAS 操作在读取旧值的同时, 也要读取版本号。
真正修改的时候,:

  • 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1。
  • 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了)。

四、JUC组件

JUC组件就是指 java.util.concurrent 这个包下面的类。

4.1 Callable接口

Callable 接口就和java.lang包下的Runnable接口的定位差不多,只不过给了我们一个返回值,在Runnable接口要重写run方法,而在Callable接口中要重写call方法。

在使用Callable接口传入线程时需要借助JUC下的另一个类FutureTask。

使用例子一般如下:

public class Demo {public static void main(String[] args) {//创建实现了Callable接口的方法Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {return null;}};//将实现Callable接口的对象作为参数传入FutureTask,泛型参数要一致FutureTask<Integer> futureTask = new FutureTask<>(callable);//将FutureTask对象作为参数传入Thread thread = new Thread(futureTask);}
}

4.2 ReentrantLock类

ReentrantLock类可重入互斥锁,和synchronized的定位是差不多的。

ReentrantLock 的用法:

  • public void lock(): 加锁, 如果获取不到锁就死等。
  • public boolean trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁。
  • public void unlock(): 解锁。
    使用这个类加锁需要我们手动解锁,为避免解锁执行不到的情况,我们一般将unlock放在finally代码块中。
		ReentrantLock locker = new ReentrantLock();try {locker.lock();}finally {locker.unlock();}

ReentrantLock与synchronized的区别:

  • ReentrantLock是类,是Java代码实现的,synchronized是关键字由JVM使用c++代码实现的。
  • ReentrantLock是需要lock()方法加锁,unlock()方法解锁。synchronized是通过进出代码块加锁解锁。
  • ReentrantLock还提供了tryLock()方法可以设置超时时间,不会一直阻塞,加锁成功返回true,调用者可以根据返回值自己操作。
  • ReentrantLock还提供了公平锁的实现,默认是非公平锁,但是构造方法传参true实现公平锁。
  • ReentrantLock搭配的通知机制是Condition类,可以更精确控制唤醒某个指定的线程,相比wait / notify功能更强大。

4.3 Semaphore类

信号量,:用来表示 “可用资源的个数”。 本质上就是一个计数器,能够协调多个线程之间的资源调配。
最主要的就是申请资源(P操作,调用acquire方法)释放资源(V操作,调用release方法)

  • 可以把信号量想象成是停车场的展示牌: 当前有车位 100 个。表示有 100 个可用资源。
  • 当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作)
  • 当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)
  • 如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.

4.4 CountDownLatch类

CountDownLatch类的作用:同时等待 N 个任务执行结束。(就像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。)

使用多线程的时候,经常会把一个大任务拆分为多个子任务,使用CountDownLatch衡量子任务完成,让整个任务完成。

  • 构造 CountDownLatch 实例, 参数传入任务个数,初始化 10 表示有 10 个任务需要完成。
  • 每个任务执行完毕, 都调用 countDown()方法 . 在 CountDownLatch 内部的计数器同时自减.
  • 主线程中使用 await()方法; 阻塞等待所有任务执行完毕后wait结束。 相当于计数器为 0 了。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

玄机-应急响应- Linux入侵排查

一、web目录存在木马&#xff0c;请找到木马的密码提交 到web目录进行搜索 find ./ type f -name "*.php" | xargs grep "eval(" 发现有三个可疑文件 1.php看到密码 1 flag{1} 二、服务器疑似存在不死马&#xff0c;请找到不死马的密码提交 被md5加密的…

沈阳乐晟睿浩科技有限公司抖音小店运营创新

在当今这个数字化迅猛发展的时代&#xff0c;电子商务已经成为推动经济增长的重要引擎。而在电商的广阔舞台上&#xff0c;短视频与直播带货的崛起无疑是最为耀眼的明星之一。作为这一领域的佼佼者&#xff0c;抖音小店凭借其庞大的用户基础和独特的算法优势&#xff0c;吸引了…

使用Python和Parsel库爬取CSDN博客文章专栏并生成Markdown链接列表的导航

引言 今天&#xff0c;我将分享如何使用Python的requests和parsel库来爬取CSDN博客的文章&#xff0c;并生成一个Markdown格式的链接列表导航页。 我在整理这个专栏Linux基础操作合集的文章合集时&#xff0c;发现想要给这个专栏的文章做个导航合集页很麻烦&#xff0c;虽然直…

哪个牌子的宠物空气净化器好?口碑好的宠物空气净化器推荐!

哪个牌子的宠物空气净化器好&#xff1f;作为一名家电测评博主&#xff0c;我发现市面上宠物空气净化器的牌子越来越多了&#xff0c;很多厂家都看中了宠物行业的红利&#xff0c;想来分一杯羹&#xff0c;这就导致很多技术不成熟的产品流入了市场。今年我测试了50多台宠物空气…

数据清理——确保数据质量的关键步骤

简介 在数据分析和机器学习中&#xff0c;数据清理是预处理过程中的重要一环。良好的数据清理能够提高数据的质量&#xff0c;从而提升模型的准确性和可靠性。本篇文章将深入探讨数据清理的几个关键知识点&#xff0c;包括缺失值处理、数据不一致问题和噪声处理。通过详细的概…

isp框架代码理解

一、整体框架如下&#xff1a; 1 外层的src中 1.1 从camera.c->task.c&#xff1a;封装了3层&#xff0c;透传到某个功能的本级。 1.2 core.c和capability.c中实现&#xff1a;开机初始化加载参数。2. plat/src中 2.1 fun.c中继task.c又透传了一层&#xff1b;以及最后功能…

状态机模型

文章目录 一、大盗阿福二、股票买卖 IV三、股票买卖 V四、设计密码4.1kmp题目4.2设计密码 一、大盗阿福 题目链接 #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N 1e5 10; int f[N][2]; int main() {int…

MATLAB——矩阵操作

内容源于b站清风数学建模 数学建模清风老师《MATLAB教程新手入门篇》https://www.bilibili.com/video/BV1dN4y1Q7Kt/ 目录 1.MATLAB中的向量 1.1向量创建方法 1.2向量元素的引用 1.3向量元素修改和删除 2.MATLAB矩阵操作 2.1矩阵创建方法 2.2矩阵元素的引用 2.3矩阵…

一:Linux学习笔记(第一阶段)-- 安装软件 vmware workstation 虚拟机软件 centos系统

目录 学习计划&#xff1a; 资源准备 虚拟机软件&#xff1a;就别自己找了 现在换网站了 下载比较费劲 Centos8&#xff1a; 阿里云镜像地址下载&#xff08;下载比较版 但是有不同版本&#xff09;&#xff1a;centos安装包下载_开源镜像站-阿里云 百度网盘地址&#xff…

如何在Linux系统中使用Zabbix进行监控

如何在Linux系统中使用Zabbix进行监控 Zabbix简介 安装Zabbix 在Debian/Ubuntu系统中安装 在CentOS/RHEL系统中安装 配置Zabbix数据库 创建数据库 导入数据库 配置Zabbix服务器 访问Zabbix Web界面 完成初始配置 配置Zabbix Agent 安装Agent 配置Agent 添加主机到Zabbix 创…

uniapp编译多端项目App、小程序,input框键盘输入后

项目场景&#xff1a; uniapp编译后的小程序端&#xff0c;app端 在一个输入框 输入消息后&#xff0c;点击键盘上的操作按钮之后键盘不被收起&#xff0c;点击其他发送按钮时&#xff0c;键盘也不被收起。 问题描述 在编译后的app上普通的事件绑定&#xff0c;tap,click在发…

代码随想录day15 二叉树(3)

文章目录 day11 栈与队列(2)栈与队列的总结 day13 二叉树&#xff08;1&#xff09;day14 二叉树&#xff08;2&#xff09;day15 二叉树&#xff08;3&#xff09; day11 栈与队列(2) 逆波兰表达式求值 https://leetcode.cn/problems/evaluate-reverse-polish-notation/ 逆…

【C#】搭建环境之CSharp+OpenCV

在我们使用C#编程中&#xff0c;对图片处理时会用到OpenCV库&#xff0c;以及其他视觉厂商提供的封装库&#xff0c;这里因为OpenCV是开源库&#xff0c;所以在VS资源里可以直接安装使用&#xff0c;这里简单说明一下搭建的步骤及实现效果&#xff0c;留存。 1. 项目创建 1.1…

环形运输距离Conveyor Belts

Conveyor Belts 题面翻译 传送带 题目描述 传送带 $ m_n $ 是一个大小为 $ n \times n $ 的矩阵&#xff0c;其中 $ n $ 是一个偶数。矩阵由顺时针移动的同心带组成。 换句话说&#xff0c;当 n 2 n2 n2 时&#xff0c;传送带矩阵就是一个 2 2 2 \times 2 22 的矩阵&a…

ffmpeg视频滤镜:添加边框-drawbox

滤镜介绍 drawbox 官网链接 > FFmpeg Filters Documentation 这个滤镜会给视频添加一个边框。 滤镜使用 参数 x <string> ..FV.....T. set horizontal position of the left box edge (default "0")y <string&…

CPU算法分析LiteAIServer视频智能分析平台噪声检测功能在视频监控中的应用与优势

在视频监控系统中&#xff0c;噪声问题一直是影响视频画面清晰度和可用性的关键因素。这些噪声可能源于多种因素&#xff0c;如低光环境、摄像机传感器的高灵敏度或编码压缩过程中的失真等。为了应对这些挑战&#xff0c;CPU算法分析LiteAIServer引入了噪声检测功能&#xff0c…

HTB:BoardLight[WriteUP]

目录 连接至HTB服务器并启动靶机 1.How many TCP ports are listening on BoardLight? 2.What is the domain name used by the box? 3.What is the name of the application running on a virtual host of board.htb? 4.What version of Dolibarr is running on Board…

mysql 5.7实现组内排序(连续xx天数)

需求&#xff1a;查询出连续登录的用户及其连续登录的天数 我先说一下思路&#xff1a;要实现连续登录的判断&#xff0c;可以找一下他们之间的规律。这里我拿一个用户来说&#xff0c;如果这个用户在1、2、3号都有登录记录&#xff0c;可以对这个用户的数据按照时间排序&…

★ Linux ★ 基础开发工具的使用(上)

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将和大家一起学习 linux 基础开发工具的使用~ 目录 壹 Linux编辑器 - vim使用 1.1 vim的基本概念 1.2 vim正常模式命令集 1.2.1 插入模式 1.2.2 移动光标命令 1.2.3 编辑命令 1.3 vim末行模式命令集 贰 Lin…

solidworks学习6吊环-20241030

solidworks学习6吊环 图 1 使用到的命名&#xff1a;拉伸曲面&#xff0c;旋转曲面&#xff0c;镜像实体&#xff0c;剪裁曲面&#xff0c; 前视基准面绘制 图 2 绘制旋转轴 图 3 旋转曲面 图 4 上视基准面绘制&#xff0c;标准圆边尺寸的时候需要按住shift键标注&#x…