当前位置: 首页 > 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.lryc.cn/news/576764.html

相关文章:

  • Git常见使用
  • 现代 JavaScript (ES6+) 入门到实战(四):数组的革命 map/filter/reduce - 告别 for 循环
  • 【记录】Ubuntu创建新用户,并可远程连接
  • 【大语言模型入门】—— 浅析LLM基座—Transformer原理
  • 自然语言处理NLP期末复习
  • 解锁云原生微服务架构:搭建与部署实战全攻略
  • 小米路由器 AX3000T自定义子网掩码
  • 大模型小模型选型手册:开源闭源、国内国外全方位对比
  • AtCoder Beginner Contest 412
  • 2025.6GESP四级(编程题详解)
  • 基于云的平板挠度模拟:动画与建模-AI云计算数值分析和代码验证
  • 鸿蒙5:自定义构建函数
  • 提示技术系列——生成知识提示
  • HTTP中常见的Content-Type
  • 【HuggingFace】模型选型策略指南(读懂config.json)
  • RAG工作原理
  • 什么是MPC(多方安全计算,Multi-Party Computation)
  • LeetCode Hot 100 最大子数组和
  • HarmonyOS NEXT仓颉开发语言实战案例:小而美的旅行App
  • NLP文本增强——随机删除
  • HarmonyOS NEXT仓颉开发语言实战案例:健身App
  • 野生动物检测数据集介绍-5,138张图片 野生动物保护监测 智能狩猎相机系统 生态研究与调查
  • rabbitmq springboot 有哪些配置参数
  • ONLYOFFICE 协作空间 企业版使用秘籍-8.使用虚拟数据房间,处理机密文档更安全
  • 生物实验室安全、化学品安全
  • MATLAB变音系统设计:声音特征变换(男声、女声、童声互转)
  • fvcom 网格文件grd制作
  • 日线周线MACD指标使用图文教程,通达信指标
  • 什么是零知识证明(Zero-Knowledge Proof, ZKP)
  • BF的数据结构题单-省选根号数据结构 - 题单 - 洛谷 计算机科学教育新生态