【JavaEE】多线程(6)

news2025/1/11 0:23:26

一、用户态与内核态

【概念】

用户态是指用户程序运行时的状态,在这种状态下,CPU只能执行用户态下的指令,并且只能访问受限的内存空间

内核态是操作系统内核运行时的状态,内核是计算机系统的核心部分,CPU可以执行所有指令,可以访问所有内存空间

【 两者切换原因】

当用户程序需要执行一些需要操作系统支持的操作时,需要将用户态切换到内核态

【举例】

线程的阻塞与唤醒就需要用户态与内核态切换

当线程被阻塞时,线程会从用户态切换到内核态,操作系统内核会处理阻塞请求将线程的状态设置为阻塞,并将其添加到等待队列中

等线程被唤醒时,操作系统会在内核态中将线程的状态改为就绪并将其从等待队列中移除,切换到用户态后,继续执行用户态下的指令

【注意】

用户态与内核态之间切换的开销非常大,因此,减少不必要的用户态与内核态之间的切换对于系统性能和效率提高很重要

二、锁策略

2.1 什么是锁策略

锁策略是指在多线程编程中,这把锁在加锁、解锁、锁冲突时都会怎么做

2.2 乐观锁 vs 悲观锁

悲观锁认为多个线程访问同⼀个共享变量冲突的概率较大,会在每次访问共享变量之前都去真正加锁
乐观锁认为多个线程访问同⼀个共享变量冲突的概率不大,并不会真的加锁,而是直接尝试访问数据
在访问的同时识别当前的数据是否出现访问冲突.
 

【Java中synchronized是哪种锁】

synchronized既是乐观锁也是悲观锁,因为它支持自适应

synchronized在开始的时候会使用乐观锁,当发现锁竞争的次数增加时会切换为悲观锁

2.3 重量级锁 vs 轻量级锁

一般认为悲观锁就是重量级锁,乐观锁就是轻量级锁

重量级锁加锁的过程做的事情多——重量;轻量级锁加锁的过程做的事情少——轻量

synchronized是一个轻量级锁,如果锁冲突比较严重就会变成重量级锁

2.4 自旋锁 vs 挂起等待锁

自旋锁是轻量级锁的一种典型实现方式,下面是自旋锁一段伪码:

while (true) {
    if (锁是否被占用) {
        continue;
    }
    获取到锁
    break;
}

CPU在忙等、空转,如果获取锁失败,就立即再尝试获取锁,无限循环,直到获取到锁为止;消耗了更多的CPU资源,但是锁一旦被释放,就会第一时间拿到锁

自旋锁轻量的原因:一方面自旋锁避免了线程的阻塞与唤醒的开销,减少了性能的消耗;另一方面自旋锁一般适用于线程占用锁时间较少的场景,不会造成过多CPU资源

拿到锁的速度更快,但消耗CPU
 

挂起等待锁是重量级锁的一种典型实现方式,借助系统中的线程调度,如果当前锁被占用,该线程尝试获取锁,就会挂起(阻塞状态),直到这个锁被释放,系统调度到这个线程,该线程才会尝试获取这个锁

挂起等待锁重量的原因:需要进行线程的阻塞与唤醒,有较多的用户态与内核态之间的切换,重量

拿到锁的速度更慢,节省CPU

synchronized 轻量级锁部分是基于自旋锁实现的,重量级锁部分是基于挂起等待锁实现的

2.5 可重入锁 vs 不可重入锁

可重入锁:同一个线程,针对同一把锁,连续加锁两次,不会死锁

不可重入锁:同一个线程,针对同一把锁,连续加锁两次,会死锁

synchronized是可重入锁

2.6 公平锁 vs 非公平锁

公平锁:严格按照先来后到的顺序来获取锁,哪个线程等待的时间长,哪个线程就先拿到锁

非公平锁:多个线程随机获取到锁,和线程等待时间无关

synchronized属于非公平锁

2.7 互斥锁 vs 读写锁

synchronized是互斥锁

读写锁是一个比较特殊的锁,先来看下面几个有关线程安全场景:

  • 两个线程只读一个共享数据,不会发生线程安全问题
  • 两个线程写一个共享数据,会发生线程安全问题
  • 两个线程一个都一个写,会发生线程安全问题

读写锁拥有一下功能:

  • 读锁和读锁之间不会发生互斥——有利于降低锁冲突的概率
  • 写锁和写锁之间会发生互斥
  • 读锁和写锁之间会发生互斥

synchronized不是读写锁,因为加上synchronized后,即使是两个都只读共享变量也会产生互斥

三、synchronized 实现原理

3.1 特点

  • 既是悲观锁,也是乐观锁
  • 既是轻量级锁,也是重量级锁;轻量级锁基于自旋锁实现,重量级锁基于挂起等待所实现
  • 可重入锁
  • 非公平锁
  • 是互斥锁,不是读写锁

3.2 synchronized 自适应

什么是偏向锁:

代码首次执行synchronized对对象加锁时并不是真正加锁,而是作一个标记,如果后续没有其他线程针对这个对象加锁的话,就一直保持这种状态,直到解锁,这样就减少了系统开销

当后续有其他线程占用同一个锁对象加锁时,才会真正加锁,此时就已升级成了轻量级锁

3.3 锁消除

锁消除是一种锁优化策略

当在代码中写了加锁的操作,编译器&JVM会对你当前的代码进行检查,看这个锁加的是否合适,如果完全没必要加锁,就会把加锁操作优化掉

比如在单线程的环境下进行加锁操作,该操作就会被编译器优化掉

3.4 锁粗化

锁的粒度:当加锁的范围内,进行的操作越多,锁的粒度越粗,反之,锁的粒度越细

在保证逻辑等价的情况下,为了避免频繁加锁解锁,编译器会将多次细粒度的锁,合并成一次粗粒度的锁

四、CAS

4.1 什么是CAS

CAS(compare and swap),意为比较和交换,一个CAS设计以下操作

假设内存中的值为V,旧的预期值为A,要修改的值为B

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

下面是一段CAS的伪码:

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

注意:上述代码并不是原子的,真实的CAS是一个原子硬件指令,改代码只是辅助理解

当多个线程针对某一资源进行CAS操作,只有一个线程操作成功,但是其他线程并不会阻塞,而是收到操作失败的信号

4.2 CAS是怎么实现的

简而言之,是因为硬件方面提供了支持,软件层面才可以做到,由于CPU提供了CAS对应的硬件指令,因此操作系统内核也能够完成这样的操作,之后OS会提供出CAS的api,JVM对OS提供的api进一步的封装,我们便可以在Java中使用CAS操作了

4.3 CAS 的应用

1)原子类

标准库中提供了 java.util.concurrent.atomic 包,里面的类都是基于CAS实现的原子类

我们以 AtomicInteger 类为例:

public class Demo {
    public static void main1(String[] args) {

        AtomicInteger count = new AtomicInteger(1);

        count.getAndIncrement(); // count++

        count.incrementAndGet(); // ++count

        count.getAndDecrement(); // count--

        count.decrementAndGet(); // --count

        count.getAndAdd(100); //count += 100

    }
}

上述代码的加加减减操作都是原子的,没有用到任何加锁操作
接下来以其中一个方法为例进行详细剖析:看getAndIncrement()的伪代码

class AtomicInteger {

    private int value;

    public int getAndIncrement() {
        int oldValue = value;
        while ( CAS(value, oldValue, oldValue+1) != true) {
            oldValue = value;
        }
        return oldValue;
    }
}

假设两个线程同时调用getAndIncrement

1. 两个线程都读取value的值到oldValue中(oldValue是一个局部变量,每个线程都有自己的栈)

2. 线程1先执行CAS,发现oldValue 和 value 相同,则直接对value 赋值 oldValue + 1,注意这里是getAndIncrement,所以先获取再加加,所以返回的是oldValue,但其实value已经加1了

3. 线程2再执行CAS的时候,发现value 和 oldValue不相等,则进入循环,在循环里重新获取value的值并赋值给oldValue

4. 线程2第二次执行CAS,发现oldValue 和 value相同,于是执行赋值操作

5. 线程1和线程2针对同一个变量进行加加操作,整个过程线程是安全的并且没有用到锁

2)实现自旋锁

上述线程2在循环中重新将value赋值给oldValue的操作很像自旋锁的实现逻辑,实际上,自旋锁就是基于CAS实现的,来看伪代码:

public class SpinLock {
    private Thread owner = null; //此时owner处于未加锁状态

    public void lock(){

        while(!CAS(this.owner, null, Thread.currentThread())){
        }

    }

    public void unlock (){
        this.owner = null;
    }
}

代码中owner用来追踪加锁的线程,如果为null,代表代码中没有任何一个线程加锁,接下来有一个线程1调用lock()方法进行加锁,执行CAS,发现owner为空,则直接进行加锁,并owner指向这个加锁的线程,CAS执行成功,返回true,取反后跳出while循环

此时又来一个线程2也要进行加锁(假设这里锁对象和线程1相同),调用lock()方法,发现owner不为空,说明有其他线程进行了加锁,那就进入循环,并不断尝试CAS操作

当线程1解锁后,调用unlock()方法,此时owner为空,线程2执行CAS操作成功,成功加锁并跳出循环

4.4 CAS 的 ABA 问题

CAS的核心是:比较发现相等→交换,CAS希望的是数据从来没改变过(相等)但是某些情况,可能会有其他线程将数据从A→B→A,CAS并不能判断数据中途是否有发生改变,这就是ABA问题

ABA在一些极端情况下可能产生bug,开下面一段取款的伪代码:

void 取款 () {
    int oldBalance = balance; // balance 为当前账户余额
    // CAS执行成功,取款500
    while (!CAS(balance, oldBalance, balance - 500)) {}

}

假如我的初衷就是取500块钱,取款机创建了两个线程来并发执行-500操作,我们希望一个-500成功,一个-500失败

此时如果加一个转账的操作就会引发bug

如何避免ABA问题:

上述场景中,用余额来判定本身就不太科学,因为余额会发生改变,容易引发ABA问题

引入版本号,约定版本号只能加 不能减,每次操作余额版本号都要+1,如果版本号没有改变,余额就一定没有改变过


🙉本篇文章到此结束

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

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

相关文章

故障处理--kuboard无法访问,etcd磁盘空间不足

问题现象: kuboard页面报错 排查过程: 1、查看kuboard是否正常。 2、查看kuboard容器的日志: docker logs -f --tail10 kuboard 大概内容如下: levelerror msg"failed to rotate keys: etcdserver: mvcc: database sp…

unity3d—demo(实现给出图集名字和图片名字生成对应的图片)

目录 实现给出图集名字和图片名字生成对应的图片: 代码示例: dic: 键 是图集名称 值是一个字典 该字典键是图片名称 值是图片,结构如图: 测试代码: 结果: SpriteRenderer 讲解: Resour…

工业异常检测-CVPR2024-新的3D异常数据合成办法和自监督网络IMRNet

论文:https://arxiv.org/pdf/2311.14897v3.pdf 项目:https://github.com/chopper-233/anomaly-shapenet 这篇论文主要关注的是3D异常检测和定位,这是一个在工业质量检查中至关重要的任务。作者们提出了一种新的方法来合成3D异常数据&#x…

WPF编写工业相机镜头选型程序

该程序满足面阵和线阵的要求。 前端代码 <Window x:Class"相机镜头选型.MainWindow" Loaded"Window_Loaded"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…

springai结合ollama

目录 ollama 介绍 使用 下载&#xff1a; 安装&#xff1a; 点击这个玩意next就行了。 运行 spring ai使用ollama调用本地部署的大模型 加依赖 配置yml 写代码 ollama 介绍 官网&#xff1a;Ollama Ollama是一个用于部署和运行各种开源大模型的工具&#xff1b; …

Linux 统信UOS 设置程序“桌面快捷方式”与“开机自启动”

最近在统信uos系统 arm64架构上进行QT程序的开发&#xff0c;基本开发完毕后&#xff0c;开始着手准备程序的开机自启动模块&#xff0c;因为一般来说&#xff0c;程序在客户现场使用都是需要开机自启的。 然后在百度海淘&#xff0c;很少有这类相关的博客介绍&#xff0c;有一…

WiFi受限不再愁,电脑无网络快速修复指南

有时在试图连接WiFi时&#xff0c;会发现网络连接受限&#xff0c;或无法正常访问互联网。这种情况不仅影响了工作效率&#xff0c;还可能错过重要的信息。那么&#xff0c;究竟是什么原因导致了电脑WiFi连接受限呢&#xff1f;又该如何解决这一问题呢&#xff1f;小A今天就来教…

java注解(一):什么是注解?什么是元注解?如何自定义注解?注解的原理是什么?

目录 1、什么是注解&#xff1f; 2、什么是元注解 1、Target() 2、Retention() 3、Documented 4、Inherited 3、如何自定义注解以解使用 4、注解的原理 本篇文章主要是介绍注解的概念、原理&#xff0c;以及通过代码演示4种元注解、如何自定义注解。通过反编译的形式进…

【Docker】Linux与Windows系统安装Docker+Docker上简单安装MySQL

一、Windows安装Docker 由于我在许多平台搜索Windows下安装Docker的方法&#xff0c;都提到了Win10家庭版无法直接安装Docker。个人电脑就是Win10家庭版&#xff0c;本着实践出真知的想法&#xff0c;个人在本机Win10家庭版实验结果为需要采用下述传统手动安装的办法&#xff…

阿里云整理(二)

阿里云整理 1. 访问网站2. 专业名词2.1 域名2.2 域名备案2.3 云解析DNS2.4 CDN2.5 WAF 1. 访问网站 用户使用浏览器访问网站大体分为几个过程&#xff1a; 用户在浏览器输入域名URL&#xff0c;例如www.baidu.com。 不过&#xff0c;浏览器并不知道为该域名提供服务的服务器具…

Robust Depth Enhancement via Polarization Prompt Fusion Tuning

paper&#xff1a;论文地址 code&#xff1a;github项目地址 今天给大家分享一篇2024CVPR上的文章&#xff0c;文章是用偏振做提示学习&#xff0c;做深度估计的。模型架构图如下 这篇博客不是讲这篇论文的内容&#xff0c;感兴趣的自己去看paper&#xff0c;主要是分享环境&…

Oceanbase离线集群部署

准备工作 两台服务器 服务器的配置参照官网要求来 服务器名配置服务器IPoceanbase116g8h192.168.10.239oceanbase216g8h192.168.10.239 这里选oceanbase1作为 obd机器 oceanbase安装包 选择社区版本的时候自己系统的安装包 ntp时间同步rpm包 联网机器下载所需的软件包 …

Ajax:回忆与节点

一点回忆 面对我的Ajax学习&#xff0c;实现前后端交互&#xff0c;最开始我采用的使用网络寻找intellij IDEA Ultimate破解方法&#xff0c;然后最终成功&#xff0c;然后按照相关教程配置java ee项目&#xff0c;然后中间又去配置了Tomcat服务器&#xff0c;然后又去学习了一…

《鸣潮》运行时电脑提示“d3dx9_41.dll丢失”是什么原因?“缺失d3dx9_41.dll文件”的解决方法和预防方案

游戏运行时文件丢失与报错解决方案&#xff1a;《鸣潮》提示“d3dx9_41.dll丢失”怎么办&#xff1f; 大家好&#xff0c;我是一名在软件开发领域有着丰富经验的从业者。在游戏爱好者的世界里&#xff0c;遇到游戏运行时提示文件丢失或损坏的情况并不少见。今天&#xff0c;我…

ARINC 标准全解析:航空电子领域多系列标准的核心内容、应用与重要意义

ARINC标准概述 ARINC标准是航空电子领域一系列重要的标准规范&#xff0c;由航空电子工程委员会&#xff08;AEEC&#xff09;编制&#xff0c;众多航空公司等参与支持。这些标准涵盖了从飞机设备安装、数据传输到航空电子设备功能等众多方面&#xff0c;确保航空电子系统的兼…

vue中pdf.js的使用,包括pdf显示,跳转指定页面,高亮关键词

目录 一、下载pdf.js 二、引入到本地的项目中 三、实现预览pdf 四、跳转到指定页面 五、利用pdf里面的find查找关键词 六、修改页面大小为实际大小 一、下载pdf.js https://github.com/mozilla/pdf.js 里面有很多的版本&#xff0c; 高版本的可能浏览器不兼容或者还要考…

【不稳定的BUG】__scrt_is_managed_app()中断

【不稳定的BUG】__scrt_is_managed_app函数中断 参考问题详细的情况临时解决方案 参考 发现出现同样问题的文章: 代码运行完所有功能&#xff0c;仍然会中断 问题详细的情况 if (!__scrt_is_managed_app())exit(main_result);这里触发了一个断点很奇怪,这中断就发生了一次,代…

Chrome控制台 网站性能优化指标一览

打开chrome-》f12/右键查看元素-》NetWrok/网络 ctrlF5 刷新网页&#xff0c;可以看到从输入url到页面资源请求并加载网页&#xff0c;用于查看资源加载&#xff0c;接口请求&#xff0c;评估网页、网站性能等&#xff0c;如下图&#xff1a; request、stransferred、resour…

第六节、S曲线加减速转动【51单片机-TB6600驱动器-步进电机教程】

摘要&#xff1a;本节介绍步进电机S曲线加减速方法&#xff0c;包含了一般形式S曲线方程的推导&#xff0c;以其适配51单片机TB6600驱动器S曲线运动数据的生成。本节所述S曲线均为标准Sigmod曲线的简称&#xff0c;与其他算法所述的7段式S曲线或者5段式S曲线相比&#xff0c;二…

【青牛科技】拥有两个独立的、高增益、内部相位补偿的双运算放大器,可适用于单电源或双电源工作——D4558

概述&#xff1a; D4558内部包括有两个独立的、高增益、内部相位补偿的双运算放大器&#xff0c;可适用于单电源或双电源工作。该电路具有电压增益高、噪声低等特点。主要应用于音频信号放大&#xff0c;有源滤波器等场合。 D4558采用DIP8、SOP8的封装形式 主要特点&#xff…