通俗易懂讲乐观锁与悲观锁

浅谈乐观锁与悲观锁

乐观锁和悲观锁是Java并发编程中的两个概念。使用乐观锁和悲观锁可以解决并发编程中数据不一致性、死锁、性能差等问题,乐观锁与悲观锁的实行方式不同,所以其特性也不近相同,下文将详细介绍两者的特性与适用场景。

《熊出没》相信大家都了解过,接下来我将用《熊出没》中吉吉国王的视角来通俗易懂的讲述乐观锁与悲观锁。

悲观锁-总有刁民想害朕

吉吉国王在昨天摘了很多香蕉,在睡觉前没有吃完,于是它将剩下的香蕉存储起来留在第二天吃,由于吉吉国王身居高位,对于个人的饮食安全比较在意,因此它在扒出香蕉前总是在想:总有刁民想害朕,一定有其他猴子偷吃(数据减少、扣库存)本王的香蕉,或者给本王的香蕉下毒(修改数据),在吃之前我一定要好好检查一下。

悲观锁总是假设最坏的情况,即每次访问数据的时候,数据均被其他线程修改,所以悲观锁在每次使用时都会对所需资源进行上锁,如果其他线程获取该资源时会被阻塞,需要等待当前线程将资源释放。

Java悲观锁举例

synchronized关键字:synchronized关键字可以用来修饰方法或代码块,确保在同一时间只有一个线程可以访问被synchronized修饰的方法或代码块。当一个线程进入synchronized代码块时,会自动获取对象的锁,其他线程需要等待该线程释放锁才能访问。

public synchronized void synchronizedMethod() {// synchronized方法体
}// 或者
public void someMethod() {synchronized(this) {// synchronized代码块}
}

ReentrantLock类:ReentrantLock是Java中的一种可重入锁,它提供了与synchronized类似的加锁和释放锁的功能,但相比synchronized更加灵活,可以支持公平锁和非公平锁,并且提供了更多的高级功能,如可中断锁、定时锁等。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Example {private Lock lock = new ReentrantLock();public void someMethod() {lock.lock(); // 获取锁try {// 锁保护的代码块} finally {lock.unlock(); // 释放锁}}
}

ReadWriteLock接口:ReadWriteLock接口提供了读写锁的功能,可以允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。ReentrantReadWriteLockReadWriteLock接口的默认实现。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class Example {private ReadWriteLock rwLock = new ReentrantReadWriteLock();public void readMethod() {rwLock.readLock().lock(); // 获取读锁try {// 读取共享资源} finally {rwLock.readLock().unlock(); // 释放读锁}}public void writeMethod() {rwLock.writeLock().lock(); // 获取写锁try {// 写入共享资源} finally {rwLock.writeLock().unlock(); // 释放写锁}}
}

悲观锁适用场景与缺陷

适用场景:悲观锁的核心观念是:每次方式资源时,该资源均被修改,因此悲观锁适用于写多、读少的业务场景

悲观锁缺陷:由于悲观锁每次使用时都需要对资源进行加锁,如果与其他线程存在资源竞争关系则可能会导致死锁互相阻塞的问题。

乐观锁-人之初,性本善

知识补充:

乐观锁版本机制: 一般是在数据表中加上一个数据版本号 version 或update_time字段,表示数据被修改的次数。

爱吃蜂蜜的熊大和熊二采集了一罐蜂蜜,它们约定每人每天吃一口(并发更新),吃完后在罐子上划上属于自己的线(线-版本),以证明自己吃过。熊二有时会贪吃,偶尔吃完一口还想再吃一口(数据被修改)。有一天,熊二不受控制地吃了两次,画了两道线。轮到熊大吃蜂蜜时,它发现罐子上的横线数量与上次吃蜂蜜时不一致(版本不一致),熊大意识到熊二又在嘴馋了,心里暗自嘀咕着这家伙真是没完没了。

出于熊大和熊二两兄弟间的信任,或者相信“天下还是好人多”,乐观锁总是相信共享资源没有被其他线程修改过,判断逻辑是通过版本机制或者CAS(compare and swap)算法实现。

版本机制

假设线程1要使用乐观锁对id为1的数据做修改,在修改前,需要先查询数据数据版本,然后再执行其他逻辑,在执行其他逻辑的期间,该数据可能被其他线程所修改,在下边的案例中修改了对应的数据,此时线程1并不知道其他线程修改了数据,为了判断数据是否被修改,线程1在更新时在where条件中校验数据版本,如果数据被修改过,则version版本不可能为1,因此,可以通过update语句的影响行数判断数据是否被修改。如果修改失败,则根据业务可使用重试机制。

create table orders
(id      int auto_incrementprimary key,price   decimal       null comment '金额',version int default 1 null comment '版本'
);# 线程1查看数据版本
select version from orders where id = 1;# 线程2修改了orders
update orders set price = 20.00, version = 2 where id = 1 and version = 1;# 线程1做修改orders的操作
update orders set price = 30.00, version = 2 where id = 1 and version = 1;

使用Java代码模拟乐观锁的情况:

import java.util.concurrent.atomic.AtomicInteger;class OptimisticLock {private AtomicInteger version = new AtomicInteger(0);private String data;public OptimisticLock(String data) {this.data = data;}// 读取数据public String readData() {return data;}// 更新数据public void updateData(String newData) {// 模拟在更新数据之前检查版本int oldVersion = version.get();// 模拟执行更新逻辑前,其他线程更新数据时,版本已经发生变化simulateConcurrency(); if (oldVersion != version.get()) {System.out.println("Data update failed due to concurrent modification.");//可按业务需求来进行重试return;}data = newData;version.incrementAndGet();System.out.println("Data updated successfully. New version: " + version.get());}// 模拟并发访问,延迟一段时间private void simulateConcurrency() {try {Thread.sleep(1000); // 模拟并发情况下的延迟} catch (InterruptedException e) {e.printStackTrace();}}
}public class Main {public static void main(String[] args) {OptimisticLock lock = new OptimisticLock("Initial data");// 线程1尝试更新数据new Thread(() -> {lock.updateData("Updated by Thread 1");}).start();// 线程2尝试更新数据new Thread(() -> {lock.updateData("Updated by Thread 2");}).start();}
}

在这个示例中,OptimisticLock 类代表一个具有乐观锁机制的数据对象。version 字段用于记录数据的版本号,每次更新数据时,版本号都会递增。在 updateData 方法中,首先检查旧版本和当前版本是否一致,如果一致则更新数据并递增版本号,否则认为更新失败。模拟了并发情况下的延迟和版本检查。

CAS算法

CAS:compoare and swap,比较和交换,CAS也可以理解为一种版本机制:比较期望值和待更新值是否一致,如果一致,则修改当前值为新值。CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。

CAS中的三个角色:

  • 待更新值:Var,简写V
  • 期望值:Expected
  • 新值:New(待写入值)

一只熊一天能吃一次蜂蜜,熊二贪嘴吃了两次蜂蜜,罐子上有两个杠,熊大期望熊二吃了一次,罐子上一个杠,轮到熊大吃蜂蜜时,熊大实际看到罐子上两个杠,与期望值不符,熊大没有吃蜂蜜,去告诉了妈妈(值不相等,不修改,抛出异常),第二天熊二知错就改,吃了一次蜂蜜,熊大看到与自己期望的一条杠一致,开心的吃了蜂蜜,画上了第二条杠(当前值与期望值一致,写入新值)。

CAS算法ABA问题

如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的 "ABA"问题。

ABA 问题的解决思路是在变量前面追加上版本号或者时间戳。JDK 1.5 以后的 AtomicStampedReference 类就是用来解决 ABA 问题的,其中的 compareAndSet() 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

该段原地址:Java Guide ABA问题

悲观锁和乐观锁适用场景

悲观锁在使用时都会把公共资源进行加锁,其他线程处于阻塞状态,性能相较于乐观锁较低,综合以上,悲观锁适合写多、读少的业务场景

乐观锁在使用时会根据版本机制判断公共资源是否被修改过,如果被修改过会执行重试机制,如果写入频率较高,则会频繁进行重试,占用服务器CPU资源,综合以上,乐观锁适合读多、写少的场景

后续内容文章持续更新中…

近期发布。


关于我

👋🏻你好,我是Debug.c。微信公众号:种棵代码技术树 的维护者,一个跨专业自学Java,对技术保持热爱的bug猿,同样也是在某二线城市打拼四年余的Java Coder。

🏆在掘金、CSDN、公众号我将分享我最近学习的内容、踩过的坑以及自己对技术的理解。

📞如果您对我感兴趣,请联系我。

若有收获,就点个赞吧,喜欢原图请私信我。

wallhaven-gpkd77.jpg

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

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

相关文章

OFDM 802.11a的FPGA实现(十六)长训练序列:LTS(含Matlab和verilog代码)

目录 1.前言2.原理3.Matlab生成长训练序列4.硬件实现5.ModelSim仿真6.和Matlab仿真结果对比 原文链接(相关文章合集): OFDM 802.11a的xilinx FPGA实现 1.前言 在之前已经完成了data域数据的处理,在构建整个802.11a OFDM数据帧的时…

【Redis】Redis面试和工作中十有八九会遇到的问题

1. 数据类型 常用的Redis数据类型有5种,分别是: String、List、Set、SortedSet、Hash 还有一些高级数据类型,比如Bitmap、HyperLogLog、GEO等,其底层都是基于上述5种基本数据类型。因此在Redis的源码中,其实只有5种数…

React: memo

React.memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。 const MemoizedComponent memo(SomeComponent, arePropsEqual?)React 通常在其父组件重新渲染时重新渲染一个组件。你可以使用 memo 创建一个组件,当它的父组件重新渲染时,只要它的新…

Docker Compose常用命令与属性

大家好,今天给大家分享Docker Compose的常用命令,以及docker-compose文件的属性。Docker Compose 是一个用于定义和运行多容器 Docker 应用应用的重要工具。它通过一个配置文件(docker-compose.yml)来详细定义多个容器之间的关联、…

软考中级-软件设计师 (十一)标准化和软件知识产权基础知识

一、标准化基础知识 1.1标准的分类 根据适用的范围分类: 国际标准指国际化标准组织(ISO)、国际电工委员会(IEC)所制定的标准,以及ISO所收录的其他国际组织制定的标准。 国家标准:中华人民共和…

Redis20种使用场景

Redis20种使用场景 1缓存2抽奖3Set实现点赞/收藏功能4排行榜5PV统计(incr自增计数)6UV统计(HeyperLogLog)7去重(BloomFiler)8用户签到(BitMap)9GEO搜附近10简单限流11全局ID12简单分…

在大型项目上,Python 是个烂语言吗?

在开始前我有一些资料,是我根据网友给的问题精心整理了一份「Python的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!! python项目超过5万行&#x…

前端之电力系统SVG图低代码

其实所有的图形都是由点&#xff0c;线&#xff0c;面组成的。点线面可以组成一个设备。下面就简单讲讲点线面是怎么画的吧 对于线&#xff0c;可以用path <g><path:d"M ${beginX},${beginY} L ${endX},${endY}":stroke-width"lineWidth":strok…

【Pip】pip 安装第三方包异常:[SSL:CERTIFICATE_VERIFY_FAILED]解决方案

pip 安装第三方包异常:[SSL:CERTIFICATE_VERIFY_FAILED] 大家好 我是寸铁&#x1f44a; 总结了一篇pip 安装第三方包异常:[SSL:CERTIFICATE_VERIFY_FAILED]✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 报错 今天在安装第三方包时报错如下: 解决方案 本质上是需要指定信任的镜像…

C#【进阶】泛型

1、泛型 文章目录 1、泛型1、泛型是什么2、泛型分类3、泛型类和接口4、泛型方法5、泛型的作用思考 泛型方法判断类型 2、泛型约束1、什么是泛型2、各泛型约束3、约束的组合使用4、多个泛型有约束思考1 泛型实现单例模式思考2 ArrayList泛型实现增删查改 1、泛型是什么 泛型实现…

pytest教程-46-钩子函数-pytest_sessionstart

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_report_testitemFinished钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_sessionstart钩子函数的使用方法。 pytest_sessionstart 是 Pytest 提供的一个钩子函数&#xff0c…

python使用opencv实现手势识别并控制ppt

需要使用到的包 from collections import dequeimport cv2 import numpy as np import math import shutilimport sys import os import time#这个求出现频率最高的太慢了&#xff0c;所以把它放弃了 from collections import Counter准备好安装包后需要获取图片 def star():…

DockerFile介绍与使用

一、DockerFile介绍 大家好&#xff0c;今天给大家分享一下关于 DockerFile 的介绍与使用&#xff0c;DockerFile 是一个用于定义如何构建 Docker 镜像的文本文件&#xff0c;具体来说&#xff0c;具有以下重要作用&#xff1a; 标准化构建&#xff1a;提供了一种统一、可重复…

SQL注入漏洞常用绕过方法

SQL注入漏洞 漏洞描述 Web 程序代码中对于用户提交的参数未做过滤就直接放到 SQL 语句中执行&#xff0c;导致参数中的特殊字符打破了原有的SQL 语句逻辑&#xff0c;黑客可以利用该漏洞执行任意 SQL 语句&#xff0c;如查询数据、下载数据、写入webshell 、执行系统命令以及…

企业OA办公系统开发笔记:1、搭建后端环境

文章目录 企业办公系统&#xff1a;搭建环境一、项目介绍1、介绍2、技术栈3、项目模块4、数据库 二、搭建环境1、搭建后端1.1、搭建父工程clfwzx-oa-parent1.2、搭建工具类父模块common1.3、搭建工具类common的子模块1.4、搭建实体类模块model和项目模块service-oa 2、配置依赖…

【前端】CSS基础(3)

文章目录 前言1. CSS常用元素属性1.1 字体属性1.1.1 字体1.1.2 字体大小1.1.3 字体颜色1.1.4 字体粗细1.1.5 文字样式 前言 这篇博客仅仅是对CSS的基本结构进行了一些说明&#xff0c;关于CSS的更多讲解以及HTML、Javascript部分的讲解可以关注一下下面的专栏&#xff0c;会持续…

做软件测试如何突破月薪20K?

IT行业从事技术岗位&#xff0c;尤其对于测试来说&#xff0c;月薪20K&#xff0c;即便在北上广深这类一线城市薪水也不算低了&#xff0c;可以说对于大部分测试岗位从业者来说&#xff0c;20K都是一个坎儿。 那么&#xff0c;问题来了&#xff0c;做软件测试如何可以达到月薪…

连锁收银系统如何助力实体门店私域运营

作为实体门店&#xff0c;私域运营是提升客户黏性和增加复购率的重要策略之一。而连锁收银系统在私域运营中扮演了关键的角色&#xff0c;它不仅可以帮助门店管理客户信息和消费记录&#xff0c;还能够通过数据分析和营销功能提供个性化的服务和推广活动。下面看看连锁收银系统…

Qt 6.7 正式发布!

本文翻译自&#xff1a;Qt 6.7 Released! 原文作者&#xff1a;Qt Group研发总监Volker Hilsheimer 在最新发布的Qt 6.7版本中&#xff0c;我们大大小小作出了许多改善&#xff0c;以便您在构建现代应用程序和用户体验时能够享受更多乐趣。 部分新增功能已推出了技术预览版&a…

证照之星是什么软件 证照之星哪个版本好用?证照之星支持哪些相机 证照之星XE免费版

许多人都需要使用证件照&#xff0c;为了满足这一需求&#xff0c;人们会使用照相机、手机、电脑等工具进行拍摄。除此之外&#xff0c;市面上还存在专门的证件照拍摄软件&#xff0c;比如证照之星。那么&#xff0c;各位小伙伴是否了解证照之星哪个版本好用&#xff0c;证照之…