当前位置: 首页 > news >正文

【Java EE初阶 --- 多线程(进阶)】锁策略

乐观学习,乐观生活,才能不断前进啊!!!

我的主页:optimistic_chen

我的专栏:c语言 ,Java

欢迎大家访问~
创作不易,大佬们点赞鼓励下吧~

文章目录

  • 前言
  • 几种锁策略
    • 悲观锁与乐观锁
    • 重量级锁与轻量级锁
    • 挂起等待锁与自旋锁
    • 普通互斥锁与读写锁
    • 可重入锁与不可重入锁
    • 公平锁与非公平锁
  • synchronized原理
    • 锁升级
    • 锁消除
    • 锁粗化
  • 原子性
    • 原子类
    • CAS
      • CAS的缺陷 ABA问题
  • 完结

前言

经过前面几次博客的总结,对于多线程编程,我们有了一定了解,接下来我们会更加深入了解的关键是 · 锁 ·,针对不同情况下,我们将采用不同的锁策略,对以后工作合理使用锁更加得心应手。

几种锁策略

悲观锁与乐观锁

首先, 这里不针对某一种具体的锁,而是某个锁具有“悲观”或者“乐观”特性

悲观锁:假设线程对锁的竞争十分激烈,就需要对这种情况额外做一些操作。

<举个例子>假设办公室只有一个老师,但是有10个同学同时想要问问题,就会出现一个同学想要问题时,老被其他某一个同学占用。这个时候就需要额外操作来问问题了。

乐观锁:假设线程对锁的竞争不激烈,一般不会出现冲突

<举个例子>假设目前只有两个同学,只有一个老师,两个同学需要问老师的时间很可能不会冲突,正常问问题就行。

总结:这里描述的都是加锁时(问题)的场景。

重量级锁与轻量级锁

之前有说过,锁的特性是“原子性”的,这种特性的根本是CPU硬件发出的“微指令”

重量级锁:在悲观场景下,付出更多的代价解决问题 ——》更低效

不断的在内核态与用户态之间切换,调用大量资源

轻量级锁:在乐观场景下,付出较小的代价解决问题—— 》更高效

尽可能少的在内核态与用户态之间切换,减少资源消耗

总结:用来遇到不同场景下的不同解决方案

挂起等待锁与自旋锁

挂起等待锁:如果获取锁失败,直接挂起等待.实现重量级锁的典型表现。

属于操作系统内核级别的操作,加锁的时候有竞争,使该线程进入阻塞状态,后续需要内核唤醒。虽然获取锁的时间更长,但是这个过程不消耗CPU资源。(竞争激烈)

自旋锁:如果获取锁失败,⽴即再尝试获取锁, ⽆限循环, 直到获取到锁为⽌.轻量级锁的典型表现

属于应用程序级别的操作,加锁的时候很少有竞争,一般不进入阻塞,而是通过忙等来等待。虽然回获取锁的时间更短,但是忙等的过程中一直消耗CPU资源。(竞争不激烈)

那我们之前的synchronized是乐观还是悲观锁呢?

成年人的世界当然都要了~, 它既是乐观锁又是悲观锁。属于自适应锁,也就是看情况而论。 synchronized作为大佬设计好的作品,为我们提前做好了一切准备。 如果竞争不激烈,此时synchronized就会按照轻量级锁(自旋)使用;如果竞争激烈,此时synchronized就会按照重量级锁(挂起等待)使用。

普通互斥锁与读写锁

普通互斥锁就是synchronized正常加锁、解锁
读写锁:读方式加锁、写方式加锁、解锁

<举个例子>如果你给读和写都加上普通互斥锁,意味着锁冲突非常严重。而读写锁就能确保读锁和读锁之间不会互斥(阻塞),保证线程安全的前提下,降低锁冲突的概率,提高效率。

适用于读多、写少的情况,典型的例子就是教务系统。

可重入锁与不可重入锁

之前说synchronized时,它就是一个“可重入锁”,就是一个线程,一把锁,连续加锁多次,是否会死锁。
判定标准很明确:
1. 锁要记录当前是哪个线程拿到的这把锁
2. 使用计数器,记录当前线程加锁了多少次,在合适时候进行解锁。
如果一个线程,一把锁,连续加锁多次,没有变成死锁,那它就是可重入锁;反之就是不可重入锁。

公平锁与非公平锁

公平锁:遵循先来后到原则,是先来谁先获取锁。
非公平锁:遵循概率相等原则,谁都有可能获取锁(随机)。

我们知道操作系统内部的线程调读就是随机,如果没有任何限制,锁就是非公平锁;如果要实现公平锁,就需要依赖额外的数据结构,记录先后顺序。

synchronized原理

锁升级

在这里插入图片描述

锁消除

编译器优化的一种体现,前面提到编译器优化是为了提出 volatile 关键字,这里是为了判定当前代码逻辑是否需要加锁,如果不需要,但是你写了synchronized,就会自动把synchronized去掉。(在编译器100%确认的情况触发)

锁粗化

锁的粒度:加锁和解锁之间,包含的代码越多(实际执行的时间),就认为锁的粒度就越粗。

实际应用中,加锁之后的解锁是为了锁能够被其他线程使用,但是可能并没有其他线程来抢占这个锁,那么就没必要去解锁,,JVM就会把锁粗化,避免频繁释放锁,消耗资源。

在这里插入图片描述
注意:如果三个任务本身都需要加锁,那么粗化为一个锁就可以,但是如果只有两个任务需要加锁,粗化为一把锁就不合适了,能够并行的任务,变成串行了。

原子性

原子类

//private static int count=0;//使用原子类 ,代替intprivate static AtomicInteger count=new AtomicInteger(0);public static void main(String[] args) {Thread t1=new Thread(()->{for(int i=0;i<20000;i++){//count++count.getAndIncrement();}});Thread t2=new Thread(()->{for(int i=0;i<20000;i++){count.getAndIncrement();}});t1.start();t2.start();try{t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count.get());}

在这里插入图片描述

CAS

  1. compare and swap(比较并交换)
          内存地址  寄存器的值  另一个寄存器的值
boolean CAS(address,expectValue,swapValue){if(&address==expectedValue){//判断内存的值是否和寄存器的值一样&address==swapValue;//相同,就把另一个寄存器的值交换给内存return ture;//实质上是赋值}return false;}

CAS操作严格意义上讲,它是CPU的一条指令,它的来源是:操作系统对CPU指令(CAS)进行封装,提供一些API,可以在C++重被调用,而JVM又是基于C++实现的,JVM也能够调用CAS这样的原子性操作。

使用这种原子性操作既保证了性能,又保证了线程安全。

  1. 基于CAS实现自旋锁
private Thread ower=null;//如果为null,锁是空闲public void lock(){//通过CAS看当前锁是否被某个线程持有//如果锁被使用,则自旋等待//如果锁空闲,那么ower设为当前需要加锁的线程while(!CAS(this.ower,null,Thread.currentThread())){}}public void unlock(){this.ower=null;}

CAS的缺陷 ABA问题

CAS能够线程安全,核心是先比较内存和寄存器是否“相等”

int oldBalance=balance;
CAS(balance,oldBalance,oldBalance-500);
<举个例子> 假设我们存款有1000,需要取出500,那么按照CAS方式来取款,内存和寄存器1比较相等,再把寄存器2 扣除500 的操作交换给内存,但是在这时,又有人给你转账500,内存此时变为1000,又和寄存器1的值相等,继续进行交换(赋值),内存最后的值还是500。你的存款痛失500!!

ABA问题的核心在于判断中间是否有其他线程修改值,那么我们约定一个概念“版本号”:只要存在修改,版本号就+1

int oldVersion=version;
if(CAS(version,oldVersion,oldVersion+1)){balance-=500;
}

完结


可以点一个免费的赞并收藏起来~
可以点点关注,避免找不到我~ ,我的主页:optimistic_chen
我们下期不见不散 ~ ~ ~

http://www.lqws.cn/news/571195.html

相关文章:

  • 构建创意系统:驾驭Audition与Photoshop的AI之力,洞悉原子化设计哲学
  • Cursor1.1.6安装c++插件
  • MyBatis实战指南(八)MyBatis日志
  • 【数据集处理】基于 3D-GloBFP建筑轮廓数据 栅格化建筑数据(完整Python代码)
  • Day.46
  • 水果维生素含量排名详表
  • 【硬核数学】9. 驯服“梯度下降”:深度学习中的优化艺术与正则化技巧《从零构建机器学习、深度学习到LLM的数学认知》
  • 【JavaSE】反射学习笔记
  • 中州养老:学会设计数据库表
  • WebRTC(十三):信令服务器
  • Spring事件驱动模型核心:ApplicationEventMulticaster初始化全解析
  • 图书管理系统练习项目源码-前后端分离-使用node.js来做后端开发
  • NV064NV065美光固态闪存NV067NV076
  • 申论审题训练
  • DEPTHPRO:一秒内实现清晰的单目度量深度估计
  • 云端可视化耦合电磁场:麦克斯韦方程组的应用-AI云计算数值分析和代码验证
  • Leetcode百题斩-双指针
  • 电容屏触摸不灵敏及跳点问题分析
  • PyEcharts教程(010):天猫订单数据可视化项目
  • ISP Pipeline(9):Noise Filter for Chroma 色度去噪
  • H3C-路由器DHCPV6V4配置标准
  • 如何通过自动化减少重复性工作
  • GitHub vs GitLab 全面对比报告(2025版)
  • Java面试宝典:基础三
  • Vue中keep-alive结合router实现部分页面缓存
  • Spring生态创新应用
  • 【Redis#4】Redis 数据结构 -- String类型
  • 用户行为序列建模(篇七)-【阿里】DIN
  • AlphaFold3安装报错
  • 【系统分析师】2021年真题:论文及解题思路