锁策略之干货分享,确定不进来看看吗?️️️

news2024/12/24 3:34:06

🌈🌈🌈今天给大家分享的是关于锁策略方面的基础知识。 

清风的CSDN博客

🛩️🛩️🛩️希望我的文章能对你有所帮助,有不足的地方还请各位看官多多指教,大家一起学习交流!

✈️✈️✈️动动你们发财的小手,点点关注点点赞!在此谢过啦!哈哈哈!😛😛😛

目录

前言

一、常见的锁策略 

 1.1 乐观锁 vs 悲观锁

 1.2 读写锁

1.3 重量级锁 vs 轻量级锁

1.4 自旋锁

1.5 公平锁 vs 非公平锁

1.6 可重入锁 vs 不可重入锁  

二、锁策略常见问题 

2.1 理解乐观锁和悲观锁的以及具体实现

2.2 读写锁的基本介绍 

2.3 关于自旋锁 

2.4 synchronized 是否是可重入锁 


 

前言

在正式开始了解 synchronized 之前,需要先理解一些关于锁策略的基本知识,了解乐观锁、悲观锁、公平锁、非公平锁、可重入锁、不可重入锁的一些基本概念。 

一、常见的锁策略 

 1.1 乐观锁 vs 悲观锁

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

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

简言之,乐观锁就是预测某场景中,不太会出现锁冲突的情况;悲观锁就是预测某场景中非常容易出现锁冲突。

举个例子 : 同学 A 和 同学 B 想请教老师一个问题 .
        同学 A 认为 " 老师是比较忙的 , 我来问问题 , 老师不一定有空解答 "。 因此同学 A 会先给老师发消息 : " 老师,你忙嘛? 我下午两点能来找你问个问题嘛 ?" ( 相当于加锁操作 ) 得到肯定的答复之后 , 才会真的来问问题。如果得到了否定的答复, 那就等一段时间 , 下次再来和老师确定时间。 这个是悲观锁 .
        同学 B 认为 " 老师是比较闲的 , 我来问问题 , 老师大概率是有空解答的 "。 因此同学 B 直接就来找老师。 ( 没加锁, 直接访问资源 ) 如果老师确实比较闲 , 那么直接问题就解决了 . 如果老师这会确实很忙 , 那么同学 B 也不会打扰老师, 就下次再来 ( 虽然没加锁 , 但是能识别出数据访问冲突 )。 这个是乐观锁。

 

  • 这两种思路不能说谁优谁劣, 而是看当前的场景是否合适。
  • 如果当前老师确实比较忙, 那么使用悲观锁的策略更合适, 使用乐观锁会导致 "白跑很多趟", 耗费额外的资源。
  • 如果当前老师确实比较闲, 那么使用乐观锁的策略更合适, 使用悲观锁会让效率比较低。

 Synchronized 初始使用乐观锁策略,当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略。

        就好比同学 C 开始认为 "老师比较闲的", 问问题都会直接去找老师。但是直接来找两次老师之后, 发现老师都挺忙的, 于是下次再来问问题, 就先发个消息问问老师忙不忙, 再决定是否来问问题。

 乐观锁的一个重要功能就是要检测出数据是否发生访问冲突,我们可以引入一个 "版本号" 来解决

假设我们需要多线程修改 "用户账户余额"。

设当前余额为 100,引入一个版本号 version, 初始值为 1,并且我们规定 "提交版本必须大于记录当前版本才能执行更新余额。
1) 线程 A 此时准备将其读出( version=1, balance=100 ),线程 B 也读入此信息( version=1,
balance=100 )

 

2) 线程 A 操作的过程中从其帐户余额中扣除 50( 100-50 ),线程 B 从其帐户余额中扣除 20
( 100-20 )

 

3) 线程 A 完成修改工作,将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=50
),写回到内存中

4) 线程 B 完成了操作,也将版本号加1( version=2 )试图向内存中提交数据( balance=80
),但此时比对版本发现,线程 B 提交的数据版本号为 2 ,数据库记录的当前版本也为 2 ,不
满足 “提交版本必须大于记录当前版本才能执行更新“ 的乐观锁策略。就认为这次操作失败

 1.2 读写锁

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读取之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。
读写锁 在执行加锁操作时需要额外表明读写意图,读取操作之间并不互斥,而写入操作则要求与任何操作互斥。
一个线程对于数据的访问 , 主要存在两种操作 : 读数据 写数据
  • 两个线程都只是读一个数据, 此时并没有线程安全问题,直接并发的读取即可
  • 两个线程都要写一个数据, 有线程安全问题
  • 一个线程读另外一个线程写, 也有线程安全问题
读写锁就是把读操作和写操作区分对待。Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁。 
  • ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行 加锁解锁
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进 行加锁解锁

其中:

  • 读加锁和读加锁之间, 不互斥
  • 写加锁和写加锁之间, 互斥
  • 读加锁和写加锁之间, 互斥

 读写锁特别适合于 "频繁读, 不频繁写" 的场景中。

Synchronized 不是读写锁。

1.3 重量级锁 vs 轻量级锁

锁的核心特性 "原子性", 这样的机制追根溯源是 CPU 这样的硬件设备提供的: 

  • CPU 提供了 "原子操作指令"
  • 操作系统基于 CPU 的原子指令, 实现了 mutex 互斥锁
  • JVM 基于操作系统提供的互斥锁, 实现了 synchronized ReentrantLock 等关键字和类

 注意:

        synchronized 并不仅仅是对 mutex 进行封装, 在 synchronized 内部还做了很多其他的工作

重量级锁: 加锁机制重度依赖了 OS 提供了 mutex 

  • 大量的内核态用户态切换
  • 很容易引发线程的调度

简言之,重量级锁就是:加锁开销比较大,花的时间多,占用系统资源多。(一个悲观锁,很可能是重量级锁)。

轻量级锁就是:加锁开销比较小,花的时间小,占用系统资源少。 (一个乐观锁,很可能是轻量级锁)。

这两个操作, 成本比较高,一旦涉及到用户态和内核态的切换, 就意味着 "沧海桑田" 。

轻量级锁: 加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成,实在搞不定了, 再使用 mutex。

  • 少量的内核态用户态切换
  • 不太容易引发线程调度
理解用户态 vs 内核态:
       想象去银行办业务,在窗口外, 自己办理相关业务, 这是用户态,用户态的时间成本是比较可控的。在窗口内, 工作人员做, 这是内核态,内核态的时间成本是不太可控的。如果办业务的时候反复和工作人员沟通, 还需要重新排队, 这时效率是很低的。

synchronized 开始是一个轻量级锁, 如果锁冲突比较严重, 就会变成重量级锁。

1.4 自旋锁

线程在抢锁失败后进入阻塞状态,放弃 CPU ,需要过很久才能再次被调度。但实际上, 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU, 这个时候就可以使用自旋锁来处理这样的问题。
自旋锁伪代码 :
while (抢锁(lock) == 失败) {}
如果获取锁失败 , 立即再尝试获取锁 , 无限循环 , 直到获取到锁为止。 第一次获取锁失败 , 第二次的尝试会在极短的时间内到来。一旦锁被其他线程释放 , 就能第一时间获取到锁。
理解自旋锁 vs 挂起等待锁
想象一下, 去追求一个女神,当男生向女神表白后, 女神说: 你是个好人, 但是我有男朋友了~~
挂起等待锁 : 在发现女神已经有男朋友之后,(锁被占用了),就不再继续死缠烂打了.... 过了很久很久之后, 突然了解到女神单身了,才会继续联系女神。 (注意, 这个很长的时间间隔里, 女神可能已经换了好几个男票了)。
自旋锁: 死皮赖脸坚韧不拔. 仍然每天持续的和女神说早安晚安,一旦女神和上一任分手, 那么就能立刻抓住机会上位。

自旋锁是一种典型的 轻量级锁 的实现方式:

  • 优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁。
  • 缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源。 (而挂起等待的时候是不消耗 CPU )。

 synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的。

1.5 公平锁 vs 非公平锁

 假设三个线程 A, B, C。A 先尝试获取锁, 获取成功。 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后 C 也尝试获取锁, C 也获取失败, 也阻塞等待。当线程 A 释放锁的时候, 会发生啥呢?

公平锁: 遵守 "先来后到"。B C 先来的,  A 释放锁的之后, B 就能先于 C 获取到锁。

非公平锁 : 不遵守 " 先来后到 "。B C 都有可能获取到锁。

注意:

  • 操作系统内部的线程调度就可以视为是随机的。如果不做任何额外的限制, 锁就是非公平锁, 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序。
  • 公平锁和非公平锁没有好坏之分, 关键还是看适用场景。

synchronized 是非公平锁。

1.6 可重入锁 vs 不可重入锁  

可重入锁:可重入锁的字面意思是“可以重新进入的锁,即允许同一个线程多次获取同一把锁

比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是 可重入 (因为这个原因可重入锁也叫做 递归锁 Java里只要以Reentrant 开头命名的锁都是可重入锁,而且 JDK 提供的所有现成的 Lock 实现类,包括 synchronized 关键字锁都是可重入的。

 不可重入锁:

理解 "把自己锁死:一个线程没有释放锁, 然后又尝试再次加锁。

// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待. 
lock();

       按照上述对于锁的设定, 第二次加锁的时候, 就会阻塞等待。直到第一次的锁被释放, 才能获取到第二个锁。 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作,这时候就会 死锁。

 这样的锁称为 不可重入锁

synchronized 是可重入锁。

二、锁策略常见问题 

2.1 理解乐观锁和悲观锁的以及具体实现

悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加锁。
乐观锁认为多个线程访问同一个共享变量冲突的概率不大,并不会真的加锁, 而是直接尝试访问数据。 在访问的同时识别当前的数据是否出现访问冲突。
悲观锁的实现就是先加锁(比如借助操作系统提供的 mutex), 获取到锁再操作数据,获取不到锁就等待。
乐观锁的实现可以引入一个版本号,借助版本号识别出当前的数据访问是否冲突。

2.2 读写锁的基本介绍 

  • 读写锁就是把读操作和写操作分别进行加锁
  • 读锁和读锁之间不互斥
  • 写锁和写锁之间互斥
  • 写锁和读锁之间互斥
  • 读写锁最主要用在 "频繁读, 不频繁写" 的场景中

2.3 关于自旋锁 

如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止,第一次获取锁失败, 第二次的尝试会在极短的时间内到来。 一旦锁被其他线程释放, 就能第一时间获取到锁。
相比于挂起等待锁:
  • 优点:没有放弃 CPU 资源, 一旦锁被释放就能第一时间获取到锁, 更高效。 在锁持有时间比较短的场景下非常有用。
  • 缺点: 如果锁的持有时间较长, 就会浪费 CPU 资源

2.4 synchronized 是否是可重入锁 

是可重入锁。
可重入锁指的就是连续两次加锁不会导致死锁。
实现的方式是在锁中记录该锁持有的线程身份, 以及一个计数器(记录加锁次数),如果发现当前加锁的线程就是持有锁的线程, 则直接计数自增。

🌈🌈🌈好啦,今天的分享就到这里!

🛩️🛩️🛩️希望各位看官读完文章后,能够有所提升。

🎉🎉🎉创作不易,还希望各位大佬支持一下!

✈️✈️✈️点赞,你的认可是我创作的动力!

⭐⭐⭐收藏,你的青睐是我努力的方向!

✏️✏️✏️评论:你的意见是我进步的财富!

 

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

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

相关文章

基于stm32的LCD1602与无线蓝牙温湿度显示

这一篇博客是为了实现温湿度的显示,温湿度传感器将数据穿给单片机,单片机又把数据送给LCD1602和蓝牙,让温度和湿度可以再LCD1602显示屏和手机上显示,它的执行逻辑和C51那里基本一样,就是要修改程序,在程序上…

选择排序、插入排序、希尔排序

1.选择排序 算法描述 将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集 重复以上步骤,直到整个数组有序 选择排序呢,就是首先在循环中,找到数组中最小的元素。在…

基恩士软件的基本操作(六,KV脚本的使用)

目录 什么是KV脚本? KV脚本有什么用? 怎么使用KV脚本(脚本不能与梯形图并联使用)? 插入框脚本(CtrlB) 插入域脚本(CtrlR) 区别 脚本语句(.T是字符串类…

详解原生Spring当中的事务

😉😉 学习交流群: ✅✅1:这是孙哥suns给大家的福利! ✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 🥭🥭3:QQ群:583783…

Redis实战篇笔记(最终篇)

Redis实战篇笔记(七) 文章目录 Redis实战篇笔记(七)前言达人探店发布和查看探店笔记点赞点赞排行榜 好友关注关注和取关共同关注关注推送关注推荐的实现 总结 前言 本系列文章是Redis实战篇笔记的最后一篇,那么到这里…

界面组件DevExpress Reporting v23.1新版亮点 - UX功能增强

DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表 界面组件DevExpress Reporting v23.1已于前段时间…

探讨Unity中的动画融合技术(BlendTree)

动画在游戏和虚拟现实应用中扮演着关键的角色,而动画融合技术则是使角色动作更加流畅和逼真的核心。在Unity引擎中,我们可以使用动画混合树(Blend Trees)来实现这一目标。本篇技术博客将深入讨论动画融合技术的实现原理、在Unity中…

C++ 指针详解

目录 一、指针概述 指针的定义 指针的大小 指针的解引用 野指针 指针未初始化 指针越界访问 指针运算 二级指针 指针与数组 二、字符指针 三、指针数组 四、数组指针 函数指针 函数指针数组 指向函数指针数组的指针 回调函数 指针与数组 一维数组 字符数组…

FreeRTOS调度器启动过程分析

目录 引出思考 vTaskStartScheduler()启动任务调度器 xPortStartScheduler()函数 FreeRTOS启动第一个任务 vPortSVCHandler()函数 总结 引出思考 首先想象一下如何启动第一个任务? 假设我们要启动的第一个任务是任务A,那么就需要将任务A的寄存器值…

腾讯云手动下发指令到设备-用于设备调试

打开腾讯云API Explorer,Publish Msg https://console.cloud.tencent.com/api/explorer?Productiotcloud&Version2021-04-08&ActionPublishMessagehttps://console.cloud.tencent.com/api/explorer?Productiotcloud&Version2021-04-08&ActionPub…

【模电】设置静态工作点的必要性

设置静态工作点的必要性 静态工作点为什么要设置静态工作点 静态工作点 在放大电路中,当有信号输入时,交流量与直流量共存。将输入信号为零、即直流电源单独作用时晶体管的基极电流 I B I\tiny B IB、集电极电流 I C I\tiny C IC、b - e间电压 U B E U\t…

语义分割网络FCN

语义分割是一种像素级的分类,输出是与输入图像大小相同的分割图,输出图像的每个像素对应输入图像每个像素的类别,每一个像素点的灰度值都是代表当前像素点属于该类的概率。 语义分割任务需要解决的是如何把定位和分类这两个问题一起解决&…

佛罗里达大学利用神经网络,解密 GPCR-G 蛋白偶联选择性

内容一览:G 蛋白偶联受体 (GPCRs) 是一种将细胞膜外的刺激,传递到细胞膜内的跨膜蛋白,广泛参与到人体生理活动当中。近日,佛罗里达大学的研究者测定了 GPCRs 和 G 蛋白的结合选择性,并开发了预测二者选择性的算法&…

kubernetes监控GPA安装部署

本文在于指导如何对k8s的监控GPA(Grafana,prometheus以及alertmanager)进行安装部署。 1. 介绍 Prometheus 在真正部署Prometheus之前,应了解一下Prometheus的各个组件之间的关系及作用: 1)MertricServer:是k8s集群…

朋友圈7大黄金发圈时间

众所周知,朋友圈运营是私域运营必不可少的重要环节。 因为做好朋友圈运营,能够打造形成高质量、高价值的私域流量,加快实现用户成交。 那么如何形成一个吸粉又吸金的人设,做出高质量的朋友圈发圈内容呢? 那么如何确保能…

SSM整合(注解版)

SSM 整合是指将学习的 Spring,SpringMVC,MyBatis 进行整合,来进行项目的开发。 1 项目基本的配置类 1.1 Spring 配置类 这个配置类主要是管理 Service 中的 bean,controller 层的 bean 对象是 SpringMVC 管理的 package cn.ed…

二极管:二极管的基本原理

一、认识导体、绝缘体、半导体 什么是导体? 导体 conductor ,是指电阻率很小,且容易传导电流的物质。导体中存在大量可自由移动的带电粒子,也称为载流子。在外电场的作用下,载流子作定向运动,形成电流。 …

安装配置JDK1.8

JDK1.8的下载及配置 1.进入甲骨文官网甲骨文官网往下翻找到java8并且点击windows. 2.下载Java8必须登录账号 3下载完后点击进入安装,直接下一步就可以,记住这个路径。 4.右击我的电脑进入环境配置,新增变量。 CLASSPATH .;%JAVAHOME%\lib;…

3.C程序编译步骤

目录 1 预处理 2 编译 3 汇编 4 链接 5 文件大小情况 依次执行下面4个步骤 预处理 将所有头文件展开,比如stdio.h等,展开就相当于把stdio.h中的所有代码粘贴到你的代码里。将所有的宏文件展开,像stdio.h是官方定义的头文件&#x…

C# - Opencv应用(3) 之矩阵Mat使用[图像截取粘贴、ROI操作、位运算、数学计算]

C# - Opencv应用(3) 之矩阵Mat使用[图像截取粘贴、ROI操作、位运算、数学计算] 图像读取,大小、截取、位运算图像ROI操作:粘贴赋值、滤波图像数学计算部分结果如下: 1.图像读取,大小、截取、位运算 //图…