多线程进阶(上)

2023-11-11

目录

一.常见的锁策略

二.CAS

三.synchronized的优化


一.常见的锁策略

1.乐观锁和悲观锁

乐观锁:从名字上来看就可以看出来,这个很乐观,这个会预期锁冲突的概率很低,就会认为锁即将要被解除了,不需要等待很久,因此上,乐观锁消耗的成本很少,因为做出一些准备去应对所竞争,所以做的工作就很少,而且效率会高一些

悲观锁:反之这个会预期锁冲突的概率会很高,会认为锁很长时间都不会被解除,需要等待很久很久,因此就会做出很多的准备,做更多的工作来应对锁竞争,从而消耗的成本就会多,效率也就更低一些

2.读写锁vs普通的互斥锁

普通的互斥锁,一般只有两种操作:加锁和解锁,因此只要两个线程对同一个对象进行加锁的话,就会产生互斥

而读写锁分成了三个操作加读锁,加写锁,解锁,这里和读和写分开了,如果只是进行了读操作就加读锁,如果进行了修改操作就加写锁,这样分有什么好处呢?我们知道读只是一步指令,是一个原子性的,因此读锁和读锁之间是没有互斥关系的,读锁和写锁,写锁和写锁之间才需要互斥,因为写操作不是原子性的,根据线程的随机调度,很可能出现问题,因此只要是写锁在多线程里面就会出现问题,就需要让他们互斥,这里把读操作分离出来,就是让在读和读之间不再加锁,提高效率的方法

3.重量级锁vs轻量级锁(和上面的乐观锁和悲观锁有一定重叠)

重量级锁:做的工作量大,开销更大

轻量级锁:做的工作量小,开销更小

如果锁是基于内核的一些功能进行实现的话(比如调用了操作系统提供的mutex接口),一般认为这是重量级锁(操作系统的锁会在内核中做很多的事情,比如让线程阻塞等待)

如果锁是纯用户态实现的,一般认为是轻量级锁(用户态的代码更可控更高效)

4.挂机等待锁vs自旋锁

挂起等待锁:往往是通过内核的一些机制来实现的,往往比较重,也就是上面的重量级锁(重量级锁的典型实现)

自旋锁:往往是通过用户态代码来实现的,往往较轻,(轻量级锁的典型实现)

5.公平锁vs非公平锁

公平锁:多个线程在等待一把锁的时候,遵守先来后到的规则,谁先来谁就能先获得这个锁

非公平锁:多个线程在等待一把锁的时候,不遵守先来后到的规则,后面的每个线程获得锁的概率都是均等的,不需要看谁是先来的

对于操作系统来说,本身线程的调度就是随机的(概率均等的),操作系统提供的mutex这个锁就是非公平锁,而要实现一个公平锁的话,就要使用一个队列,来在保证线程的先来后到,反而要付出更多的代价

6.可重入锁vs不可重入锁

一个线程针对一把锁,连续的加两次锁,如果出现死锁,就是不可重入锁,没有出现死锁的话,就是可重入锁,具体可以再看一下我前面对于可重入锁的解释:线程加锁关键字_栋zzzz的博客-CSDN博客

二.CAS

全称:compare and swap(比较和交换),那这个是做什么的呢?简单介绍一下:

简单来说就是拿着寄存器/某个内存中的值和另一个内存的值进行比较,如果值相同了,就把另一个寄存器/内存的值和当前的值进行交换,

举个例子:内存中的原数据V,旧的预期值A,需要修改的新值B

1.比较A和V是否相等(比较)

2.如果比较相等,将B写入到V(交换)

3.返回操作是否成功

此处的CAS指的是CPU提供的一个单独的CAS指令,通过这一条指令,就可以完成上述的过程!(本来这些不是一条指令的话,就是有可能存在线程安全问题的,而将这些操作融合为一条指令,就是原子性的了,指令就已经是最小的分割单位了,因此就不再有线程安全问题了)

CAS最大意义就是在编写多线程安全的代码的时候,有一条新的思路和方向(和锁不一样),就像刚才的比较交换逻辑,相当于是硬件直接实现出来了,通过一条指令封装号,直接就可以使用了

CAS如何帮我们解决线程安全问题?

1.基于CAS能提供实现"原子类"

java标准库提供了一组原子类,针对常用的一些int,long,int array进行了封装,可以基于CAS的方式进行修改,让其线程安全

import java.util.concurrent.atomic.AtomicInteger;
public class Demo27 {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger count = new AtomicInteger(0);//原子类,初始化一个0
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 500; i++) {
                count.getAndIncrement();//这个操作相当于num++

            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 500; i++) {
                count.getAndIncrement();//这个操作相当于num++

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count.get());//通过get方法获得到原子类内部的值
    }
}

 这个如果我们不使用这样的原子类的话出来的结果一定是在500-1000之间的,是存在问题的,而我们使用了这样的原子类,就不存在线程安全了这是基于CAS实现的++操作,这里就可以既保证线程安全,有比synchronized操作更高效,因为synchronized操作是会导致线程相互等待的,而此处的CAS是不涉及到线程阻塞等待的,因而更高效

2.基于CAS能实现自旋锁

3.这里最为重要的还是CAS里面的ABA问题 

CAS中的关键,是先比较,再交换,比较其实在比较当前值和旧值是否相同,这两个值相同的话就视为这个值中间没有发生过变化,但是真的一定没发生过变化吗?

这是存在一些问题的,有可能当前值和旧值相同中间确实没有发生过变化,但是也是有可能中间发生变化然后又变回来了呢!其实这样的问题在大多数问题下都是没有影响的,但是极端情况下也是会引起bug的!

此时我们发现结果是正确的,但是在我们引入ABA问题之后就可以结果会出现差错了 

 

此时就会发现出现差错了,本来要取款一次的结果取了两次,这就是ABA问题,那么该如何解决呢? 

 可以引入一个"版本号",这个版本号只能变大,不能变小,比较的时候就不是比较变量本身了,而是比较版本号了

利用"版本号",我们就可以很好的规避ABA问题了,这种基于版本号的方式进行多线程数据的控制,也是一种乐观锁的典型实现

三.synchronized的优化

对于前面我们学习过的synchronized锁是一个什么样的锁呢:

1.既是一个乐观锁,也是一个悲观锁(根据锁竞争的激烈程度,自适应)

2.是一个普通的互斥锁,不是读写锁

3.既是一个轻量级锁也是一个重量级锁(根据锁竞争的激烈程度,自适应)

4.轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁来实现

5.非公平锁(通过竞争获得锁)

6.可重入锁

那么对于synchronized的优化有哪些呢?

synchronized几个典型的优化手段:

1.锁膨胀/锁升级

 这个就体现了synchronized的"自适应"的能力

 根据锁的竞争,就可以自适应的进行锁状态,加锁解锁的开销也更小,这样来保证高效性

2.锁粗化/细化

这里的粗细指的是"锁的粒度"(粒度表示加锁代码涉及到的代码范围,加锁代码的范围越大,认为锁的粒度越粗,加锁代码范围越小,锁的粒度越细)

因此锁的粗化也是会在一定程度上帮助程序员来优化代码效率的! 

3.锁消除

这里表示再有些地方,可能并不需要加锁,但是你不小心加上了锁,编译器发现加上这个锁好像没啥必要,编译器就会直接把锁给去掉了

比如你在单线程里面使用了StringBuffer,Vector(这些都在标准库里面进行了加锁操作),就相当于是你在单线程里面进行了加锁,但是这里编译器会自主的做出判定,从而取消掉锁的加锁和解锁,从而提高代码的效率!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程进阶(上) 的相关文章

  • 如何使用固定数量的工作线程实现简单线程

    我正在寻找最简单 最直接的方法来实现以下内容 主程序实例化worker 执行任务的线程 Only n任务可以同时运行 When n已达到 不再有工人 开始直到计数 正在运行的线程回落到下方n 我觉得Executors newFixedThr
  • 使用 Guice 注入类集合

    我正在尝试用 Google Guice 2 0 注入东西 我有以下结构 FooAction implements Action BarAction implements Action 然后我有一个带有以下构造函数的 ActionLibrar
  • 如何将变量的全部内容发送/导出到文本文件/xml 文件/剪贴板?

    我想将实例的内容 最好以树形形式 发送给某人 打印屏幕是不行的 因为类太复杂了 您需要将输出转回实例吗 在这种情况下 其他答案都是正确的 如果您只想手动检查实例的内容 理想情况下您的类都将实现toString 你可以将其重定向到一个文件 如
  • 如何在log4j的配置文件中为文件附加器提供环境变量路径

    我有一个log4j xml配置文件 和一个RollingFileAppender我需要提供用于存储日志的文件路径 问题是我的代码将作为可运行的 jar 部署在 Unix 机器上 所以如果我传递这样的参数 value logs message
  • 如何提取文件 jre-9/lib/modules?

    In JRE 9 lib目录 至少在 Windows 上 有一个名为modules其大小约为107 MB 是否可以提取该文件或在其中列出 java 模块 我可以看到一个名为jmod可以在jdk 9 bin jmod exe 但那是为了阅读
  • Java 读取大文本文件时出现 OutOfMemoryError

    我是 Java 新手 正在读取非常大的文件 需要一些帮助来理解问题并解决它 我们有一些遗留代码 必须对其进行优化才能正常运行 文件大小仅在 10mb 到 10gb 之间变化 只有当文件开始大小超过 800mb 时才会出现启动问题 Input
  • JavaFX 2.0 FXML 子窗口

    经过多次搜索我发现了这个问题如何创建 javafx 2 0 应用程序 MDI https stackoverflow com questions 10915388 how to create a javafx 2 0 application
  • 迁移到Java 9或更高版本时是否需要切换到模块?

    我们目前正在从 Java 8 迁移到 Java 11 但是 升级我们的服务并没有我们预期的那么痛苦 我们基本上只需要更改我们的版本号build gradle文件和服务都顺利启动并运行 我们升级了库以及使用这些库的 微 服务 到目前为止没有问
  • 如何将现有的 SQLite3 数据库导入 Room?

    好吧 我在桌面上使用 SQLite3 创建了一个只需要读取的某些信息的数据库 我正在制作的应用程序不需要在此表中插入或删除信息 我在 Room 数据库层上做了相当多的谷歌搜索 所有文档都需要在构建应用程序时在 Room 中创建一个新的数据库
  • 检查 IPv4 地址是否在私有范围内

    在 Python 中 使用 IPy 模块您可以执行以下操作 gt gt gt ip iptype PRIVATE 有没有一个库或简单的方法可以在 Java 中执行相同的操作 似乎不完全是但是InetAddress有一些 isXX 方法 例如
  • Kafka Java Consumer 已关闭

    我刚刚开始使用卡夫卡 我面临着消费者的一个小问题 我用Java写了一个消费者 我收到此异常 IllegalStateException 此消费者已关闭 我在以下行中遇到异常 ConsumerRecords
  • Android volley使用RequestFuture.get()时出现超时异常

    在我的片段中 我尝试使用 TMDB 的开放电影数据库来获取有关 正在播放 电影的详细信息 如果我使用 RequestFuture get time TimeUnit 方法来执行此齐射请求 我总是会收到超时错误 如果我在 Safari 中手动
  • Java - JPanel 内有边距和 JTextArea

    我想创建这样的东西 主面板有其边距 x 并且 TextArea 位于该面板的中心 几乎填满了面板 底部是另一个具有自定义尺寸 高度 y 的面板 可以使用某些快捷方式将其切换为可见和不可见 底部面板有 FlowLayout 和几个元素 问题是
  • 是否有最新的 Facebook Java SDK? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 好像没找到最近更新的 如果没有 是否有一个好的 Java 库来执行与 Facebook 的 API 交
  • 如何在 Bean Validation 1.0 中构造 ConstraintViolationException?

    我对 javax validation API 感到困惑 我正在编写一个简单的测试来理解它 Sample sample new Sample Set
  • 改变 Java 中凯撒移位的方向

    用户可以通过选择 1 向左或 2 向右移动字母来选择向左或向右移动 左边工作正常 右边不行 现在它显示了完全相同的循环 但我已经改变了所有 and 以不同的方式进行标记 最终我总是得到奇怪的字符 如何让程序将字符向相反方向移动 如果用户输入
  • 在循环中按名称访问变量

    我正在开发一个 Android 项目 并且有很多可绘制对象 这些绘图的名称都类似于icon 0 png icon 1 png icon 100 png 我想将这些可绘制对象的所有资源 ID 添加到整数 ArrayList 中 对于那些不了解
  • 从浏览器访问本地文件?

    您好 我想从浏览器访问系统的本地文件 由于涉及大量安全检查 是否可以通过某种方式实现这一目标 或使用 ActiveX 或 Java Applet 的任何其他工作环境 请帮帮我 要通过浏览器访问本地文件 您可以使用签名的 Java Apple
  • 传递 Android DialogFragment 参数时,onCreateDialog 捆绑参数意外为 null

    我正在尝试使用 DialogFragment 在 Android 中显示一个基本对话框 并使用对话框消息的参数 如中所述StackOverflow线程 https stackoverflow com questions 15459209 p
  • 如何将库添加到 LIBGDX 项目的依赖项 gradle

    一切都在问题中 我已经尝试了在 SO 和其他网站中找到的所有答案 但没有运气 这就是我迄今为止尝试过的 adding compile fileTree dir lib include jar 到我的 build gradle adding

随机推荐

  • echarts百度地图2021最新实现方式(点,线结合)

    先在html文件中准备一个定义了高宽的 DOM 容器 div style width 100 height 900px div 引入js
  • Android免Root执行脚本,无Root可以修改权限的目录 :/data/local/tmp

    在Android中 访问data目录是需要root权限 但是有个例外那就是 data local tmp目录 这个目录通过使用ADB来打开就会拥有读写权限 注意 1 这个目录不能一级一级的打开 需要通过cd data local tmp 一
  • SQL书写规范/SQL编码规范,这一篇就够了

    SQL代码规范 SQL Structured Query Language 结构化查询语言 在数据分析 数据开发 数据库 大数据等的领域 具有不可或缺的地位 运用十分广泛 规范化的SQL代码 能够提高代码的可读性 有利于快速定位问题 更有利
  • JavaWeb学习之Ajax、JSON08

    目录 1 概念 2 JQeury实现方式Ajax方式 2 JSON 1 概念 2 语法 1 基本规则 2 获取数据 1 JS0N转为 ava对象 2 Java对象转换JS0N 1 概念 异步的 avaScript和XML 1 异步和同步 客
  • Linux: ubuntu Appium连接手机

    1 Android或者HarmonyOS 开启开发者模式 1 打开手机设置 找到 关于手机 点击进去 2 点击 版本号 多点击几次 确保手机处于 开发者模式 2 Android或者HarmonyOS 开启USB调试 1 打开手机设置 找到
  • JavaScript-判断一个字符串是否为另一个字符串的前缀或后缀(startsWith方法与endsWith方法)

    1 startWith方法 用来判断当前字符串是否以另外一个给定的子字符串开头 并根据判断结果返回 true 或 false 运用举例 var str world console log str startsWith wor 输出true
  • 阿里云Centos7下安装Mongodb

    这个安装竟然只花了十分钟不到 首先打开xshell 连接阿里云服务器 我们进去后在根目录下面用命令下载安装包 用如下xshell命令 wget https fastdl mongodb org linux mongodb linux x86
  • DNS配置异常无法上网(明明有网,但是网页打不开了?)

    原因DNS无法解析网址 可以设置一个稳定好用的 1 打开网络设置 2 选择适配器选项 3 打开正在使用的网络 选择属性 4 双击点开协议版本Internet4 5 把DNS改为自己设置 6 将DNS服务器改为下表中的任意一个 可以自己试 那
  • Python爬虫最强项目案例之——JS逆向。这波学到就是赚到。

    前言 前段时间看到有人js逆向了某手机的反馈专区 我也第一时间学习了一下 学完后一直想着凭借自己的能力 看能不能单独完成一次 拿下js逆向真正第一血 所以就有了今天的受害者 某蓝厂手机圈子的逆向 网站分析 既然选定了目标 那就开始抓包 分析
  • 【设计模式】责任链模式

    顾名思义 责任链模式 Chain of Responsibility Pattern 为请求创建了一个接收者对象的链 这种模式给予请求的类型 对请求的发送者和接收者进行解耦 这种类型的设计模式属于行为型模式 在这种模式中 通常每个接收者都包
  • ctf—web合集

    0x00 前言 本篇主要是针对不同的做题方法和思路将wp进行分类 从而更方便大家进行索引 CTF 加解密合集 CTF Web合集 网络安全知识库 文中工具皆可关注 皓月当空w 公众号 发送关键字 工具 获取 PHP 1 变量空间绕过 ctf
  • 【Linux】进程信号“疑问?坤叫算信号吗?“

    鸡叫当然也算信号啦 文章目录 前言 一 认识信号量 二 信号的产生 1 调用系统函数向进程发信号 2 由软件条件产生信号 3 硬件异常产生信号 总结 前言 信号在我们生活中很常见 下面我们举一举生活中信号的例子 你在网上买了很多件商品 再等
  • 第14课:生活中的策略模式——怎么来不重要,人到就行

    用程序来模拟生活 从剧情中思考策略模式 策略模式 策略模式的模型抽象 类图 模型说明 设计要点 优缺点 实战应用 应用场景 故事剧情 Tony 在北京漂泊了三年 在这期间有很多的美好 也有很多心酸 有很多期待 也有很多失落 可终究还是要离开
  • 使用Docker-compose部署SpringBoot项目

    目录 一 概述 二 安装 三 构建目录结构 四 控制服务的启动顺序 五 编写配置文件 六 启动Docker compose 一 概述 Docker Compose是 docker 提供的一个命令行工具 用来定义和运行由多个容器组成的应用 使
  • Kalman滤波器从原理到实现

    Kalman滤波器的历史渊源 We are like dwarfs on the shoulders of giants by whose grace we see farther than they Our study of the wo
  • 用741运算放大器搭建RC正弦振荡器:文氏电桥振荡电路

    实验目的 了解正弦振荡器的工作原理 加强仿真multisim软件的运用水平 加强对电路的理解 搭建电路的动手能力 了解个元器件之间的配合 实验电路原理图 左侧为仿真电路 右侧为振荡波形 电路原理及其分析 I RC正弦波振荡电路又称文氏电桥振
  • linux性能分析工具专题-perf(事件采样,全面性能分析)

    文章目录 概述 perf概念 perf的工具集合介绍 perf的事件介绍 perf list参看 常用perf性能查看工具使用 perf stat 运行一个命令并且统计过程事件 perf top 输出系统某个事件热度函数或者指令排序 per
  • ArcGIS Pro python 获取一百多幅栅格的平均值

    目标 我需要计算流域内的平均等效水柱高 等效水柱高的格网已经创建好 如下 每个月一个这样的栅格格网 现在有近两百个格网需要求平均等效水柱高 要求 按照时间顺序求取每一个格网的平均等效水柱高 并生成时间序列表单 如下 看到arcpro上显示栅
  • 有空就看看的leetcode1——两数之和(c++版)

    有空就看看的leetcode1 两数之和 c 版 学习前言 两数之和题目 几个需要用到的函数 解法 1 遍历法 2 哈希表法 学习前言 有点紧张 决定看看leetcode 两数之和题目 给定一个整数数组 nums 和一个目标值 target
  • 多线程进阶(上)

    目录 一 常见的锁策略 二 CAS 三 synchronized的优化 一 常见的锁策略 1 乐观锁和悲观锁 乐观锁 从名字上来看就可以看出来 这个很乐观 这个会预期锁冲突的概率很低 就会认为锁即将要被解除了 不需要等待很久 因此上 乐观锁