【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.rhkb.cn/news/484563.html

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

相关文章

SpringBoot 架构下校园失物招领系统:精准定位校园失物去向

2系统开发环境 2.1vue技术 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。 [5] 与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第…

x86处理器编程模型

为x86处理器编写程序的时候, 必须要了解x86的内核寄存器 通用寄存器 后面才扩充到了32位,又要对以前的代码进行兼容, 所以之前16位结构保留了, BP与SP主要用于对栈空间进行操作, SI和DI用来进行数据的拷贝. 段寄存器 因为早期是16位的模式,只能到65535(64KB的空间),所以后来…

Maven核心概念

Maven 介绍 Maven 官方文档是这样介绍的 Maven 的: Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a projects build, reporting and documentation from a ce…

Ubuntu的pip怎么用

第一步:查看python3版本 第二步:安装pip 第三步:可以尝试使用pip list查看 也可以尝试安装 下面这条命令可以设置永久源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

第三十九篇——条件概率和贝叶斯公式:机器翻译是怎么工作的?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么? 四、总结五、升华 一、背景介绍 数学中的概率,看似和我们的生活没关系,其实它却是…

计算机毕业设计Python轨道交通客流预测分析可视化 智慧交通 机器学习 深度学习 人工智能 爬虫 交通大数据

温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…

Spring Boot + MySQL 多线程查询与联表查询性能对比分析

Spring Boot MySQL: 多线程查询与联表查询性能对比分析 背景 在现代 Web 应用开发中,数据库性能是影响系统响应时间和用户体验的关键因素之一。随着业务需求的不断增长,单表查询和联表查询的效率问题日益凸显。特别是在 Spring Boot 项目中&#xff0…

Java 初学者的第一个 SpringBoot 系统

Java 初学者的第一个 SpringBoot 系统 对编程初学者而言,都存在一个 “第一个系统” 的问题。有些学习者找不到自己的 “第一个系统”,他们即使再努力也没有办法了解完整的系统,即使他们把教科书里的所有程序都跑通了。但是,面对…

【Vue3】详解Vue3的ref与reactive:两者的区别与使用场景

文章目录 引言Moss前沿AIVue 3响应式系统概述ref与reactive的基础概念ref与reactive的区别1. 数据类型2. 访问方式3. 响应式追踪机制4. 可变性5. 使用场景表格对比 ref与reactive的使用场景1. 选择ref的场景2. 选择reactive的场景 性能分析与优化建议1. 响应式系统的性能优势2.…

【笔记2-3】ESP32 bug:PSRAM chip not found or not supported 没有外部PSRAM问题解决

主要参考b站宸芯IOT老师的视频,记录自己的笔记,老师讲的主要是linux环境,但配置过程实在太多问题,就直接用windows环境了,老师也有讲一些windows的操作,只要代码会写,操作都还好,开发…

itextpdf读取pdf宽高问题

在使用itextpdf读取文档宽高的时候,大多数代码都是这样的: Rectangle page reader.getPageSize(pageNum); float width page.getWidth(); float height page.getHeight(); int rotation page.getRotation();这样读取的,对于标准pdf如A4等…

【nodejs】puppeteer在window下因参数scale导致重复截图问题解决

在线地址:https://textcard.shushiai.com/zh 最近构建流光卡片免费 markdown 文本转精美图片 api 的时候遇见了一个问题 👇(API 尚未公开,还在小部分内测,测试,尝试修复 bug 中) 我发现在我 w…

3、.Net UI库:MaterialSkin - 开源项目研究文章

MaterialSkin 是一个开源的 WinForms 第三方库,提供了许多仿谷歌设计风格的组件,使得 WinForms 窗体程序更加美观。以下是 MaterialSkin 的一些关键特点和使用方法: 主要特点: 仿谷歌设计风格:MaterialSkin 提供了大量…

VMware安装windows2003

一、安装vm 这一项大家应该都会,网上也有很多教程。 二、搭建Windows server 2003 1、镜像下载- 2、虚拟机安装 首先是新建虚拟机,我选的是自定义,也可以选典型 第一步默认下一步,也可以是自己的情况做修改 第二步选择稍后安…

51c自动驾驶~合集11

我自己的原文哦~ https://blog.51cto.com/whaosoft/12684932 #如何在自动驾驶的视觉感知中检测corner cases? 一篇来自德国大学的论文:“Corner Cases for Visual Perception in Automated Driving: Some Guidance on Detection Approaches“&#xf…

四、自然语言处理_02RNN基础知识笔记

1、RNN的定义 RNN(Recurrent Neural Network,循环神经网络)是一种专门用于处理序列数据的神经网络架构,它与传统的前馈神经网络(Feedforward Neural Network)不同,主要区别在于它能够处理输入数…

梯度提升树(GBDT)与房价预测案例

文章目录 什么是梯度提升树(GBDT)?核心思想GBDT 的特点 梯度提升树的应用案例:房价预测场景描述步骤详解代码详情 详细代码讲解1. 导入必要的库2. 设置中文字体支持3. 可视化真实值与预测值4. 可视化预测误差分布5. 代码的运行效果…

Rust : 生成日历管理markdown文件的小工具

需求: 拟生成以下markdown管理小工具,这也是我日常工作日程表。 可以输入任意时间段,运行后就可以生成以上的markdown文件。 一、toml [package] name "rust-workfile" version "0.1.0" edition "2021"[d…

Linux网络:代理 穿透 打洞

Linux网络:代理 & 穿透 代理正向代理反向代理 内网穿透frp 内网打洞 代理 正向代理 正向代理是一种常见的网络代理方式,它位于客户端与目标服务器之间,代表客户端向服务器发送请求,接收响应。 如图,客户端发送的…

给el-table表头添加icon图标,以及鼠标移入icon时显示el-tooltip提示内容

在你的代码中,你已经正确地使用了 el-tooltip 组件来实现鼠标划过加号时显示提示信息。el-tooltip 组件的 content 属性设置了提示信息的内容,placement 属性设置了提示信息的位置。 你需要确保 el-tooltip 组件的 content 属性和 placement 属性设置正…