【JavaEE】多线程(6)

一、用户态与内核态

【概念】

用户态是指用户程序运行时的状态,在这种状态下,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(); // ++countcount.getAndDecrement(); // count--count.decrementAndGet(); // --countcount.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执行成功,取款500while (!CAS(balance, oldBalance, balance - 500)) {}}

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

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

如何避免ABA问题:

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

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


🙉本篇文章到此结束

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

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

相关文章

Ajax:回忆与节点

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

1688:开启跨境电商新篇章

引言 在全球化贸易不断深化的今天,跨境电商已成为中小企业拓展国际市场的重要渠道。1688,作为阿里巴巴集团旗下领先的内贸平台,近年来也逐渐发力跨境电商领域,为全球买家提供了一个直通中国工厂的贸易平台。本文将带您深入了解16…

2024-12-06 Unity Addressables3——资源加载

文章目录 1 引用加载1.1 Addressables 的资源引用类1.2 加载资源1.3 加载场景1.4 释放资源 2 Label 介绍3 动态加载3.1 加载单个资源3.2 加载多个资源 Unity 版本:6000.0.26f1c1Addressables 版本:2.3.1 1 引用加载 1.1 Addressables 的资源引用类 Ass…

API设计指南:详解HTTP状态码错误解析、HTTP方法及参数命名规则

目录 1、HTTP API规范1.1 原则1.2 协议1.3 版本1.4 路径1.5 HTTP 方法(Method)1.6 过滤信息1.7 参数命名1.8 HTTP 状态码(Response Code)1.9 鉴权 2、状态码2.1 API返回基础规范2.2 常见的 HTTP 状态码2.3 API错误信息应该放到响应…

【C#】键值对的一种常见数据结构Dictionary<TKey, TValue>

在 C# 中&#xff0c;Dictionary<TKey, TValue> 是一个 键值对&#xff08;key-value&#xff09;集合&#xff0c;是一种非常常见的数据结构。它允许通过 键&#xff08;key&#xff09;来快速查找与之相关的 值&#xff08;value&#xff09;。你可以将其类比为一个映射…

Word处理表格的一些宏

目录 1、表格首行居中2、表格内容靠左上下居中&#xff08;排除首行&#xff09; 1、表格首行居中 说明&#xff1a; 遇到错误将进行捕获&#xff0c;然后继续处理下一个表格 宏&#xff1a; Sub 表格首行居中()Dim tbl As tableOn Error Resume Next 错误时继续执行下一个…

相机动态/在线标定

图1 图2 基本原理 【原理1】平行线在射影变换后会交于一点。如图所示,A为相机光心,蓝色矩形框为归一化平面,O为平面中心。地面四条黄色直线为平行且等距的车道线。HI交其中两条车道线于H、I, 过G作HI的平行线GM交车道线于M。HI、GM在归一化平面上的投影分别为JK、PN,二者会…

嵌入式Linux(SOC带GPU树莓派)无窗口系统下搭建 OpenGL ES + Qt 开发环境,并绘制旋转金字塔

树莓派无窗口系统下搭建 OpenGL ES Qt 开发环境&#xff0c;并绘制旋转金字塔 1. 安装 OpenGL ES 开发环境 运行以下命令安装所需的 OpenGL ES 开发工具和库&#xff1a; sudo apt install cmake mesa-utils libegl1-mesa-dev libgles2-mesa-dev libdrm-dev libgbm-dev2. 安…

工作:SolidWorks从3D文件导出2D的DWG或DXF类型文件方法

工作&#xff1a;SolidWorks从3D文件导出2D的DWG或DXF类型文件方法 SolidWorks从3D文件导出2D的DWG或2D DXF类型文件方法&#xff08;一&#xff09;打开3D文件&#xff08;二&#xff09;从装配体到工程图&#xff08;三&#xff09;拖出想要的角度的图型&#xff08;四&#…

【AI系统】低比特量化原理

低比特量化原理 计算机里面数值有很多种表示方式&#xff0c;如浮点表示的 FP32、FP16&#xff0c;整数表示的 INT32、INT16、INT8&#xff0c;量化一般是将 FP32、FP16 降低为 INT8 甚至 INT4 等低比特表示。 模型量化则是一种将浮点值映射到低比特离散值的技术&#xff0c;可…

Spark区分应用程序 Application、作业Job、阶段Stage、任务Task

目录 一、Spark核心概念 1、应用程序Application 2、作业Job 3、阶段Stage 4、任务Task 二、示例 一、Spark核心概念 在Apache Spark中&#xff0c;有几个核心概念用于描述应用程序的执行流程和组件&#xff0c;包括应用程序 Application、作业Job、阶段Stage、任务Task…

《跨越平台壁垒:C++ 人工智能模型在移动设备的部署之路》

在人工智能技术如日中天的今天&#xff0c;C以其卓越的性能和高效的资源利用&#xff0c;在人工智能模型开发领域占据着举足轻重的地位。然而&#xff0c;如何将 C实现的人工智能模型成功部署到移动设备上&#xff0c;让智能应用触手可及&#xff0c;成为了众多开发者亟待攻克的…

《乌合之众》笔记

1.集体会降智&#xff0c;会互相传染 2.群体是无名氏&#xff0c;因此没必要承担责任。约束个人的责任感消失 3.有意识人格的消失&#xff0c;无意识人格的得势&#xff0c;思想和感情因为暗示和互相传染而转向一个共同的方向&#xff0c;以及立刻把暗示的观念转化为行动的倾…

【ETCD】【源码阅读】ETCD启动流程源码解读

启动流程的图如下&#xff1a; 1、主函数入口 ETCD 启动的入口在 etcd/server/main.go 文件中。 package mainimport ("os""go.etcd.io/etcd/server/v3/etcdmain" )func main() {etcdmain.Main(os.Args) }这里调用了 etcdmain.Main()&#xff0c;这是 …

计算机网络-应用层/运输层

应用层 在上一篇已经提到过, 计算机网络, 最核心的功能就是个产生信息, 发送信息.而并不关注其中的接受方究竟是人, 机器. 而协议, 就是双方约定的 可以表达一定含义的 消息内容. 符合协议的, 就能够被机器解读, 并进行下一步操作, 可能还会返回一定的响应内容. 而应用层, 有…

git lfs 上传超大文件

这里写自定义目录标题 1.安装lfs2.设置LFS要管理的文件类型3.执行完上面的命令后&#xff0c;会生成一个.gitattributes文件&#xff0c;要将其上传到远程gitee仓库。这里我把.gitattributes和大文件分开上传4.上传大文件报LFS错第一种第二种 1.安装lfs cd xxx #xxx是你本地仓库…

AD20 原理图库更新到原理图

一 点击工具&#xff0c;从库更新。快捷键TL 二 点击完成 三 执行变更&#xff0c;最后点击关闭

位运算符I^~

&运算&#xff1a;上下相等才是1&#xff0c;有一个不同就是0 |运算&#xff1a;只要有1返回的就是1 ^(亦或)运算&#xff1a;上下不同是1&#xff0c;相同是0 ~运算&#xff1a;非运算&#xff0c;与数据全相反 cpu核心运算原理&#xff0c;四种cpu底层小电路 例&#xf…

ethers.js与solidity智能合约交互(hardhat项目)

1、test脚本中如何获取合约中的状态变量 //合约中public类型的状态变量支持getter()特性&#xff0c;可以直接使用部署合约的实例调用如&#xff1a;vault.token() contract Vault {//这里的token属性是public&#xff0c;自带getter()方法IERC20 public immutable token;uint…

Python毕业设计选题:基于django的民族服饰数据分析系统的设计与实现_hadoop+spider

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 民族服饰管理 看板展示 系统首页 民族服饰 服饰…