JAVAEE初阶相关内容第十一弹--多线程(进阶)

news2024/10/6 8:32:25

目录

一、常见的锁策略

1乐观锁VS悲观锁

1.1乐观锁

1.2悲观锁

2.轻量级锁VS重量级锁

2.1轻量级锁

2.2重量级锁

3.自旋锁VS挂起等待锁

3.1自旋锁

3.2挂起等待锁

4.互斥锁VS读写锁

4.1互斥锁

4.2读写锁

5.公平锁VS非公平锁

5.1公平锁

5.2非公平锁

6.可重入锁VS不可重入锁

6.1可重入锁

6.2不可重入锁

7.关于synchronized

二、CAS

1.CAS涉及的下操作:

2.CAS的应用场景

2.1实现原子类

伪代码​编辑

2.2实现自旋锁

伪代码

3.CAS中的ABA问题

三、Synchronized原理

1.锁升级/锁膨胀

1.1无锁

1.2偏向锁

1.3轻量级锁

1.4重量级锁

2.锁消除

3.锁粗化


一、常见的锁策略

接下来进行学习的内容不仅仅局限于java,任何和“锁”相关的话题,都会涉及到。

1乐观锁VS悲观锁

站在锁冲突概率的预测角度

1.1乐观锁

假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误信息,让用户决定如何去做。

1.2悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

2.轻量级锁VS重量级锁

2.1轻量级锁

加锁解锁开销较小,效率更高

2.2重量级锁

开锁解锁开销较大,效率更低

站在加锁操作的开销角度

3.自旋锁VS挂起等待锁

3.1自旋锁

典型的轻量级锁,更大几率获取到锁,加锁速度快

3.2挂起等待锁

典型的重量级锁,“傻等”,被动的等待,省下来CPU去做别的工作,加锁的时间比较长。

4.互斥锁VS读写锁

4.1互斥锁

互斥锁:就是前面用过的像synchronized这样的锁,提供加锁和解锁两个操作。如果一个线程加锁了,另一个线程也尝试加锁,就会阻塞等待。

4.2读写锁

提供三种操作:(1)针对读加锁。(2)针对写加锁。(3)解锁

基于一个事实:多线程针对同一个变量并发读,这个时候是没有线程安全问题的,也不需要加锁控制。(读写锁就是针对这种情况采取的特殊的处理)

读锁和读锁之间没有互斥。写锁和写锁之间存在互斥。写锁和读锁之间存在互斥。(当前代码中,如果只是读操作,加读锁即可,如果有写操作,加写锁。)

5.公平锁VS非公平锁

5.1公平锁

这里的公平定义为:先来后到。B比C先来的,当A释放锁后,B就能先于C获取到锁。

5.2非公平锁

不遵守先来后到,B和C都有可能获取到锁。

OS内部的线程调度就可视为是随机的,如果不做任何额外的限制,锁就是非公平的,如果要想实现公平锁,就需要额外的数据结构,来记录线程们的先后顺序。

公平锁和非公平锁之间没有好坏之分,关键还得看适用场景。

6.可重入锁VS不可重入锁

6.1可重入锁

一个线程针对一把锁,连续加锁多次不会死锁。

6.2不可重入锁

一个线程针对一把锁,连续加锁两次,出现死锁。

7.关于synchronized

(1)synchronized既是一个悲观锁,也是一个乐观锁

synchronized默认是乐观锁,但是如果发现当前锁竞争比较激烈,就会变成悲观锁。

(2)synchronized既是轻量级锁,也是重量级锁。

synchronized默认是轻量级锁,如果发现当前锁竞争比较激烈的话,就会变成重量级锁。

(3)synchronizaed这里的轻量级锁,是基于自旋锁的方式实现的。synchronized这里的重量级锁是基于挂起等待锁的方式实现的。

(4)synchronized不是读写锁。

(5)synchronized是非公平锁。

(6)synchrnized是可重入锁。

上述谈到的六种锁策略可以视为是“锁的形容词”

二、CAS

全称:Compare and swap  比较和交换

1.CAS涉及的下操作:

我们设内存中的原始数据V,旧的预期值A,需要修改的新值B

1.比较A与的V值是否相等(比较)

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

3.返回操作是否成功

此处特别的是,上述的CAS的过程并不是通过一段代码实现的,而是通过一条CPU指令完成的。也就是说CAS操作是原子的,就可以在一定程度上回避线程安全问题,所以说我们解决线程安全问题除了加锁之外就又有了一个新的方向。

CAS可以理解为是CPU给咱们提供的一个特殊的指令,通过这个指令,就可在一定程度上处理线程安全问题。

2.CAS的应用场景

2.1实现原子类

JAVA标准库中提供的类

AtomicInteger count = new AtomicInteger(0);

伪代码

使用原子类来解决线程安全问题代码实现:

创建两个线程,t1和t2,在前面的学习中,当两个线程不加锁的时候就会出现bug,所以采用了加锁策略,这里使用原子类来实现不需要加锁也可以达到预期的效果:

public class ThreadD28 {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger count = new AtomicInteger(0);
        //使用原子类来解决线程安全问题
        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                //因为java不支持运算符重载,所以只能使用普通方法来表示自增自减
                count.getAndIncrement();
            }
        });
        Thread t2 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

运行结果:

2.2实现自旋锁

伪代码

3.CAS中的ABA问题

CAS在运行中的核心,检查value和oldValue是否一致,如果一致就视为value没有被修改过,所以进行下一步的交换操作是没问题的。但是需要注意的是,这里的一致,可能是改过但是还原回来的。【买手机,可能是新机也有可能是翻新机,被销售商回收了,经过一些翻新的操作,将外壳换掉,重新包装】

下面看一个取钱的例子(概率极低!!)

以上情况发生的概率极低,但是这种问题一旦出现的话就是容易解决的,提前防患于未然是更好的选择。针对当前的问题,采取的方案就是加上一个版本号想象成初始的版本号是1,每次修改的版本号都+1,然后进行CAS的时候,就不是以金额为准了,而是以版本号为基准,此时版本号要是没变就一定没发生改变(版本号只能增长,不能降低)

三、Synchronized原理

两个线程针对同一个对象加锁,就会产生阻塞等待。

synchronized内部还有一些优化机制,存在的目的是了让这个锁更高效更好用。

1.锁升级/锁膨胀

1.1无锁

1.2偏向锁

在进行加锁的时候,首先要进入到偏向锁的状态。偏向锁并不是真正的加锁,而是占个位置,有需要才会进行加锁,没需要就不必加。相当于“懒汉模式”中提到的懒加载一样。偏向锁的状态,做个标记(这个过程是非常轻量的)如果使用锁的过程中,没有出现锁竞争在synchronized执行完之后,解除偏向锁即可,但是如果使用过程中,另一个线程也尝试加锁,这个时候就会迅速的将偏向锁升级称为真正的加锁状态,另外的一个线程也只能阻塞等待了。

1.3轻量级锁

当synchronized发生锁竞争的时候,就会从偏向锁升级为轻量级锁,此时,synchronized相当于通过自旋的方式来进行加锁的(就类似于上述的CAS中的伪代码)

1.4重量级锁

如果要是很快别人就释放了锁,自旋还是划算的,但是如果迟迟拿不到锁,一直自旋是不划算的,synchronized自旋不是无休止的,自旋到一定程度,就会在再次升级成为重量级锁(挂起等待锁)。这个锁则是基于操作系统的原生API来进行加锁的,linux原生提供了mutex一组API,操作系统内核提供的加锁功能,这个锁会影响到线程的调度。此时如果线程进行了重量级的加锁,并且发生了锁竞争,此时线程会被放在阻塞队列中,不参与CPU的调度。然后直到锁被释放,这个线程才有机会被调度到,并且有机会获取到锁。

2.锁消除

编译器智能的判定,看当前代码是否真的要加锁,如果这个场景不需要加锁,程序员加了,就会自动的把锁消除。

例如StringBuffer,关键的方法有synchronized,但是如果在单线程中使用StringBuffer,synchronized加了也白加,此时编译器就会直接将加锁操作消除。

3.锁粗化

锁的粒度:synchronized包含的代码越多,粒度就越粗,包含的代码越少,粒度就越细。

一般情况下,认为锁的粒度细一点是比较好的,加锁部分的代码是不能并发执行的,锁的粒度越细,能并发的代码就越多,反之则越少。但是有些情况下,锁的粒度粗一些就更好。

十一弹的续集会进行更新这一部分中在面试中的高频考点~

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

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

相关文章

记LGSVL Map Annotation(2)导入点云、以及地图

导入点云 内置的点云导入器工具提供了将最流行的点云文件格式&#xff08;PCD、PLY、LAS、LAZ&#xff09;转换为可用于仿真的数据所需的所有功能。 要访问点云导入器窗口&#xff0c;请在 Unity 编辑器中打开模拟器项目&#xff0c;然后导航到 Simulator/Import Point Cloud…

SpringCloud学习笔记(六)OpenFeign 服务接口调用

一、OpenFeign简介 1、OpenFeign是什么 Feign是一个声明式WebService客户端&#xff0c;使用Feign能让编写Web Service客户端更加简单。 它的使用方法是定义一个服务接口然后在上面添加注解&#xff0c;Feign也支持可拔插式的编码器和解码器&#xff0c;Spring Cloud对Feign进…

SolVES4.1学习2——导入数据运行模型

使用样例数据运行模型很容易&#xff0c;运行自己的数据要根据教程先对数据进行预处理之后根据教程导入数据。 首先新建一个solves数据库&#xff0c;之后restore。导入数据大概的流程为&#xff1a; 1、导入数据 首先使用PostGIS导入矢量数据。矢量数据包括点位和范围数据。…

grpc多语言通信之GO和DART

都是一个吗生的,找下例子 上一篇文章说到go实现的grpc方法已经实现了一个grpc的server端, 注意: 这两个项目的.proto文件应当是完全一致的,只是方法用各自的语言实现罢了 报错了: Caught error: gRPC Error (code: 12, codeName: UNIMPLEMENTED, message: grpc: Decompresso…

MySQL——命令行客户端的字符集问题

原因&#xff1a;服务器端认为你的客户端的字符集是utf-8&#xff0c;而实际上你的客户端的字符集是GBK。 查看所有字符集&#xff1a;SHOW VARIABLES LIKE character_set_%; 解决方案&#xff0c;设置当前连接的客户端字符集 “SET NAMES GBK;”

Nacos服务心跳和健康检查源码介绍

服务心跳 Nacos Client会维护一个定时任务通过持续调用服务端的接口更新心跳时间&#xff0c;保证自己处于存活状态&#xff0c;防止服务端将服务剔除&#xff0c;Nacos默认5秒向服务端发送一次&#xff0c;通过请求服务端接口/instance/beat发送心跳。 客户端服务在注册服务的…

论文解读 | 用于3D对象检测的PV-RCNN网络原创

原创 | 文 BFT机器人 01 背景 本文的背景涉及到3D物体检测&#xff0c;这是一个在自动驾驶和机器人等领域应用广泛的重要问题。在这些领域&#xff0c;LiDAR传感器被广泛用于捕捉3D场景信息&#xff0c;生成不规则且稀疏的点云数据。这些点云数据提供了理解和感知3D场景的关键…

QVector 和 QMap

QVector_QMap QVector简介 头文件&#xff1a;#include<QVector> 模块&#xff1a; QT core 功能&#xff1a; QVector类是动态数组的模板类&#xff0c;顺序容器&#xff0c;它将自己的每一个对象存储在连续的内存中&#xff0c;可以使用索引号来快速访问它们 常用…

【数据结构】树和二叉树概念

1.树概念及结构 树概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#xff0c;…

Android获取系统读取权限

在Androidifest.xml文件中加上授权语句 <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name"android.permission.READ_EXTERNAL_STORAGE"/>

Android12之/proc/pid/status参数含义(一百六十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

【LeetCode题目详解】第九章 动态规划part13 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组 (day52补)

本文章代码以c为例&#xff01; 一、力扣第300题&#xff1a;最长递增子序列 题目&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改…

Scrum敏捷开发工具的基本概念、使用方法、优势以及实际应用案例

​随着软件开发行业的不断发展和进步&#xff0c;Scrum敏捷开发工具逐渐成为了备受关注的话题。 Scrum是一种灵活且高效的项目管理方法&#xff0c;旨在提高团队协作和交付效率&#xff0c;使团队能够更快地响应变化和需求。 本文将深入探讨Scrum敏捷开发工具的基本概念、使用…

分类预测 | MATLAB实现WOA-CNN-BiGRU鲸鱼算法优化卷积双向门控循环单元数据分类预测

分类预测 | MATLAB实现WOA-CNN-BiGRU鲸鱼算法优化卷积双向门控循环单元数据分类预测 目录 分类预测 | MATLAB实现WOA-CNN-BiGRU鲸鱼算法优化卷积双向门控循环单元数据分类预测分类效果基本描述模型描述程序设计参考资料 分类效果 基本描述 1.Matlab实现WOA-CNN-BiGRU多特征分类…

敏捷项目管理实践及敏捷工具

​敏捷项目管理是一种基于敏捷开发方法的项目管理方式&#xff0c;它强调快速响应变化、持续交付价值和高效的团队合作。 1、确定敏捷宣言的价值观和原则&#xff0c;例如“以人为本”、“可用的软件”、“以客户为中心”、“拥抱变化”等&#xff0c;并在项目中始终遵循这些价…

二维差分---基础算法

书接上回 a二维数组是b二维数组的前缀和数组,b二维数组是a二维数组的差分数组,也就是说a[i][j]b[1][1]b[1][2] ......b[i][1] b[i][2] ...... b[i][j] ,下图是b的二维数组 如图,当你想要整个矩阵中的一个子矩阵都加上一个C,如果我们将b[x1][x2]加上C,那么a数组右下角所有的…

3.3 栈的表示和操作的实现

3.3.1 栈的类型定义 主要内容&#xff1a; 这段文字中包含了很多栈数据结构的基本概念和操作。 ### 3.3 栈的表示和操作的实现 #### 3.3.1 栈的类型定义 1. **数据对象**&#xff1a; - 定义了一个数据对象集合&#xff0c;记作 D {a1, a2, ..., an}&#xff0c;其…

一维的差分

差分的方法 差分实际上是前缀和的逆运算 ,这个关系和 积分与求导 的关系类似 例如有数组 ...... 和构造数组 ...... 我们要使得a数组是b数组的前缀和 ...... 那么该如何构造b数组呢? 令 , …

使用带有示例和代码的因式分解机的推荐系统

一、说明 在我之前的文章中&#xff0c;我讨论了推荐系统的基础知识、矩阵分解和神经协同过滤 &#xff08;NCF&#xff09;&#xff0c;您可以在下面的“我的博客”部分找到它们。接下来&#xff0c;这次我将通过示例和代码来探索因式分解机器。 将因子分解机用于推荐系统的一…

pytorch无法使用cuda

import torch # 如果pytorch安装成功即可导入 print(torch.cuda.is_available()) # 查看CUDA是否可用 print(torch.cuda.device_count()) # 查看可用的CUDA数量 print(torch.version.cuda) # 查看CUDA的版本号#False #0 #None 表明安装失败&#xff01;查看安装包&#xff1a;…