各种锁的概述

乐观锁与悲观锁

悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。

悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁。

如果获取锁失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常。

如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁。
在这里插入图片描述
假设updateEntry、query、update方法都使用了事务切面的方法,并且事务传播性被设置为required。

执行updateEntry方法时如果上层调用方法里面没有开启事务,则会即时开启一个事务,然后执行代码(1)。

代码(1)调用了query方法,其根据指定id从数据库里面查询出一个记录。

由于事务传播性为requried,所以执行query时没有开启新的事务,而是加入了updateEntry开启的事务,也就是在updateEntry方法执行完毕提交事务时,query方法才会被提交,就是说记录的锁定会持续到updateEntry执行结束。

代码(2)则对获取的记录进行修改,代码(3)把修改的内容写回数据库,同样代码(3)的update方法也没有开启新的事务,而是加入了updateEntry的事务。

也就是updateEntry、query、update方法共用同一个事务。

当多个线程同时调用updateEntry方法,并且传递的是同一个id时,只有一个线程执行代码(1)会成功,其他线程则会被阻塞,这是因为在同一时间只有一个线程可以获取对应记录的锁,在获取锁的线程释放锁前(updateEntry执行完毕,提交事务前),其他线程必须等待,也就是在同一时间只有一个线程可以对该记录进行修改。

乐观锁是相对悲观锁来说的,它认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排它锁,而是在进行数据提交更新时,才会正式对数据冲突与否进行检测。

具体来说,根据update返回的行数让用户决定如何去做。将上面的例子改为使用乐观锁的代码如下。
在这里插入图片描述
当多个线程调用updateEntry方法并且传递相同的id时,多个线程可以同时执行代码(1)获取id对应的记录并把记录放入线程本地栈里面,然后可以同时执行代码(2)对自己栈上的记录进行修改,多个线程修改后各自的entry里面的属性应该都不一样了。

然后多个线程可以同时执行代码(3),代码(3)中的update语句的where条件里面加入了version=#{version}条件,并且set语句中多了version=${version}+1表达式,该表达式的意思是,如果数据库里面id=#{id} and version=#{version}的记录存在,则更新version的值为原来的值加1,这有点CAS操作的意思。

假设多个线程同时执行updateEntry并传递相同的id,那么它们执行代码(1)时获取的Entry是同一个,获取的Entry里面的version值都是相同的(这里假设version=0)。

当多个线程执行代码(3)时,由于update语句本身是原子性的,假如线程A执行update成功了,那么这时候id对应的记录的version值由原始version值变为了1。其他线程执行代码(3)更新时发现数据库里面已经没有了version=0的语句,所以会返回影响行号0。

在业务上根据返回值为0就可以知道当前更新没有成功,那么接下来有两个做法,如果业务发现更新失败了,下面可以什么都不做,也可以选择重试,如果选择重试,则updateEntry的代码可以修改为如下。

在这里插入图片描述
如上代码使用retryNum设置更新失败后的重试次数,如果代码(3.1)执行后返回0,则说明代码(1.1)获取的记录已经被修改了,则循环一次,重新通过代码(1.1)获取最新的数据,然后再次执行代码(3.1)尝试更新。

这类似CAS的自旋操作,只是这里没有使用死循环,而是指定了尝试次数。

乐观锁并不会使用数据库提供的锁机制,一般在表中添加version字段或者使用业务状态来实现。乐观锁直到提交时才锁定,所以不会产生任何死锁。

公平锁与非公平锁

根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程请求锁的时间早晚来决定的,也就是最早请求锁的线程将最早获取到锁。而非公平锁则在运行时闯入,也就是先来不一定先得。
ReentrantLock提供了公平和非公平锁的实现。

  • 公平锁:ReentrantLock pairLock =new ReentrantLock(true)。
  • 非公平锁:ReentrantLock pairLock =new ReentrantLock(false)。如果构造函数不传递参数,则默认是非公平锁。

例如,假设线程A已经持有了锁,这时候线程B请求该锁其将会被挂起。当线程A释放锁后,假如当前有线程C也需要获取该锁,如果采用非公平锁方式,则根据线程调度策略,线程B和线程C两者之一可能获取锁,这时候不需要任何其他干涉,而如果使用公平锁则需要把C挂起,让B获取当前锁。
在没有公平性需求的前提下尽量使用非公平锁,因为公平锁会带来性能开销。

独占锁与共享锁

根据锁只能被单个线程持有还是能被多个线程共同持有,锁可以分为独占锁和共享锁。

独占锁保证任何时候都只有一个线程能得到锁,ReentrantLock就是以独占方式实现的。

共享锁则可以同时由多个线程持有,例如ReadWriteLock读写锁,它允许一个资源可以被多线程同时进行读操作。

独占锁是一种悲观锁,由于每次访问资源都先加上互斥锁,这限制了并发性,因为读操作并不会影响数据的一致性,而独占锁只允许在同一时间由一个线程读取数据,其他线程必须等待当前线程释放锁才能进行读取。

共享锁则是一种乐观锁,它放宽了加锁的条件,允许多个线程同时进行读操作。

什么是可重入锁

当一个线程要获取一个被其他线程持有的独占锁时,该线程会被阻塞,那么当一个线程再次获取它自己已经获取的锁时是否会被阻塞呢?如果不被阻塞,那么我们说该锁是可重入的,也就是只要该线程获取了该锁,那么可以无限次数地进入被该锁锁住的代码。

在这里插入图片描述
调用helloB方法前会先获取内置锁,然后打印输出。

之后调用helloA方法,在调用前会先去获取内置锁,如果内置锁不是可重入的,那么调用线程将会一直被阻塞。

实际上,synchronized内部锁是可重入锁。可重入锁的原理是在锁内部维护一个线程标示,用来标示该锁目前被哪个线程占用,然后关联一个计数器。

一开始计数器值为0,说明该锁没有被任何线程占用。当一个线程获取了该锁时,计数器的值会变成1,这时其他线程再来获取该锁时会发现锁的所有者不是自己而被阻塞挂起。

但是当获取了该锁的线程再次获取锁时发现锁拥有者是自己,就会把计数器值加+1,当释放锁后计数器值-1。

当计数器值为0时,锁里面的线程标示被重置为null,这时候被阻塞的线程会被唤醒来竞争获取该锁。

自旋锁

由于Java中的线程是与操作系统中的线程一一对应的,所以当一个线程在获取锁(比如独占锁)失败后,会被切换到内核状态而被挂起。

当该线程获取到锁时又需要将其切换到内核状态而唤醒该线程。

而从用户状态切换到内核状态的开销是比较大的,在一定程度上会影响并发性能。

自旋锁则是,当前线程在获取锁时,如果发现锁已经被其他线程占有,它不马上阻塞自己,在不放弃CPU使用权的情况下,多次尝试获取(默认次数是10,可以使用-XX:PreBlockSpinsh参数设置该值),很有可能在后面几次尝试中其他线程已经释放了锁。

如果尝试指定的次数后仍没有获取到锁则当前线程才会被阻塞挂起。

由此看来自旋锁是使用CPU时间换取线程阻塞与调度的开销,但是很有可能这些CPU时间白白浪费了。

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

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

相关文章

【AI视野·今日NLP 自然语言处理论文速览 第六十九期】Wed, 3 Jan 2024

AI视野今日CS.NLP 自然语言处理论文速览 Wed, 3 Jan 2024 Totally 24 papers 👉上期速览✈更多精彩请移步主页 Daily Computation and Language Papers An Autoregressive Text-to-Graph Framework for Joint Entity and Relation Extraction Authors Zaratiana Ur…

centos用yum安装mysql详细教程

1 查询安装mysql的yum源,命令如下 ls /etc/yum.repos.d/ -l 界面如下图所示,未显示mysql的安装源 2 安装mysql相关的yum源,例如: 例如:rpm -ivh mysql57-community-release-el7.rpm 要注意 mysql的版本和系统的版本匹配 mysql57-communi…

SpringBoot 如何增强PageHelper入参的健壮性

PageHelper.startPage(int pageNum, int pageSize, boolean count) 参数为外部输入,故存在异常输入场景。比如 pageNum 和 pageSize 输入的值 负数 或者 0,所以引入PageUtils来对入参进行判断矫正,从而避免引入异常。 第1步:支持…

【web】Springboot3 集成 Swagger3

文章目录 Maven 依赖配置类&#xff08;可选&#xff09;访问示例 Maven 依赖 <!--swagger3--> <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.0.2</v…

总420+,专业120+南京大学851信号与系统电子信息考研经验通信,电子信息

今年考研数学130&#xff0c;专业课120&#xff0c;总分420顺利被南京大学电通录取&#xff0c;梦圆南大&#xff0c;这一年的复习有过迷茫&#xff0c;有过犹豫&#xff0c;最后都坚持过来了&#xff0c;总结一下自己的复习经验&#xff0c;希望对大家有所帮助。数学 5-8月数…

用C语言实现动态数组Vector

代码仓库地址 1. 动态数组原理 定义一个结构体类型&#xff0c;在结构体中用指针指向一个在堆空间开辟的一块内存。 2. 编写头文件 在头文件里定义Vector的数据结构和相关操作&#xff0c;可以通过修改 “typedef char* Element;” 来修改存储的数据的类型&#xff1b; #ifn…

MPEG4Extractor

1、readMetaData 必须要找到 Moov box&#xff0c;找到 Mdat box或者 Moof box&#xff0c;并且创建了 ItemTable 大端 box 分为 box header 和 box content&#xff1a; box header由8个字节组成&#xff0c;前面四个字节表示这个box 的大小&#xff08;包含这个头的8字节&a…

【Docker】Docker基础

文章目录 安装使用帮助启动命令镜像命令容器命令 安装 # 卸载旧版本 sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine # 设置存储库 sudo yum install -y yum-utils …

QT自定义信号和槽

信号和槽 介绍实现创建文件对teacher的h和cpp文件进行处理对student的h和cpp文件进行处理对widget的h和cpp文件进行处理 介绍 Qt中的信号和槽是一种强大的机制&#xff0c;用于处理对象之间的通信。它们是Qt框架中实现事件驱动编程的核心部分。 信号&#xff08;Signal&#x…

pgAdmin和asdf postgres的安装

安装pgAdmin&#xff1a; curl https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo apt-key addsudo sh -c echo "deb https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmi…

MyBatisPlus学习一:快速入门

前言 前面快速学习了Mybatis&#xff0c;现在开始快速学习MyBatisPlus 学习教程&#xff1a; 黑马mybatis教程全套视频教程&#xff0c;2天Mybatis框架从入门到精通 黑马程序员最新MybatisPlus全套视频教程&#xff0c;4小时快速精通mybatis-plus框架 简介 MyBatisPlus 是…

使用AutoDecoder自动解码器实现简单MNIST特征向量提取

AutoDecoder 自动解码器&#xff08;AD&#xff09;是论文"DeepSDF: Learning Continuous Signed Distance Functions for Shape Representation" 中使用的一种方法&#xff0c;与传统编码-解码结构不同&#xff0c;AD无编码器&#xff0c;仅有一个解码器。解码器实…

K-means 聚类算法分析

算法简述 K-means 算法原理 我们假定给定数据样本 X &#xff0c;包含了 n 个对象 &#xff0c;其中每一个对象都具有 m 个维度的属性。而 K-means 算法的目标就是将 n 个对象依据对象间的相似性聚集到指定的 k 个类簇中&#xff0c;每个对象属于且仅属于一个其到类簇中心距离…

Python中调用matplotlib库三维可视化图像像素曲面分布

为了更直观的从3D视角观察一副图像的像素分布&#xff0c;且拖动观察没一个像素细节&#xff0c;可以使用下面代码实现。 目录 一、代码二、效果展示 一、代码 使用代码修改修改的地方如下&#xff1a; 具体实现代码如下: import numpy as np import matplotlib.pyplot as …

windows系统如何查看扇区?

windows系统如何查看扇区&#xff1f; 首先&#xff0c;我们按WindowsR 弹出"运行"对话框&#xff0c;打开文本框输入"MSINFO32.EXE"命令 展开左侧"组件"节点 接下来&#xff0c;我们选择"组件|存储|磁盘"文件夹 在其里面即可查看硬盘…

欧科云链研究院:奔赴2024,Web3与AI共振引爆数字时代潘多拉魔盒

出品&#xff5c;欧科云链研究院 2024年&#xff0c;Web3与AI两个数字科技的巅峰碰撞&#xff0c;欧科云链研究院探索AI与Web3的技术融合&#xff0c;与澎湃科技联合发布2024年展望&#xff0c;原标题为《2024年展望&#xff1a;Web3与AI共振引爆可信数字社会》&#xff0c;共…

Linux系统下gitee使用git提交代码

Linux系统下gitee使用git提交代码 一、安装配置git1.1 在 Linux 中安装 git&#xff0c;并生成授信证书1.2 将SSH key 添加到 ssh-agent1.2 将SSH key 添加到你的gitee账户 二、gitee 的使用2.1 下载项目到本地 三、上传gitee三步走3.1 三板斧第一招&#xff1a;git add3.2 三板…

编译原理期末大题步骤——例题

一、预测分析方法步骤 提取左公因子&#xff0c;消除左递归判断文法是否为LL(1)文法若是&#xff0c;构造预测分析表&#xff1b;否则&#xff0c;不能进行分析。根据预测分析表对输入串进行分析 例子&#xff1a; 文法G[E]&#xff1a; …

【Python】不一样的Ansible(一)

不一样的Ansible——进阶学习 前言正文概念Ansible CorePlugins和Modules 插件插件类型编写自定义插件基本要求插件选项文档标准编写插件 添加一个本地插件注册为内置插件指定插件目录 其他一些技巧更改Strategy 结语 前言 Ansible 是一个极其简单的 IT 自动化引擎&#xff0c…

ros gazebo机械臂仿真,手动控制与MoveIt自动控制

本文总结归纳古月居胡春旭ros机械臂教程&#xff0c;给出了一些error的解决方法&#xff0c;补充了通过python运行moveit。十分建议去看github huchunxu源代码的repository。 创建机械臂的xacro模型 首先创建一个工作空间&#xff0c;在工作空间中创建arm_description功能包。…