常见的锁策略

目录

一.乐观锁 vs 悲观锁

 二.轻量级锁 vs 重量级锁

 三.自旋锁 vs 挂起等待锁

四.互斥锁 vs 读写锁

五.可重入锁 vs不可重入锁


一.乐观锁 vs 悲观锁

悲观锁 :
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
乐观锁:
假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

简单来说,就是锁的实现者,预测接下来锁冲突的概率是大还是不大.根据这个冲突的概率,决定接下来该怎么做.

  • 乐观锁:预测接下来冲突概率不大
  • 悲观锁:预测接下来冲突概率比较大

这样就导致最终要做的事是不一样的. 

通常来说,悲观锁一般做的工作会更多一点,效率会更低一点,而乐观锁做的工作会更少一点,效率更高.但是这也并不绝对,要根据实际情况来判断使用.

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

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

 我们来看下面这个例子:

 

 

 二.轻量级锁 vs 重量级锁

  • 轻量级锁,加锁解锁过程更快更高效
  • 重量级锁,加锁解锁过程更慢更低效

这两种锁虽然和乐观悲观锁不是一回事,但是确实有一定的重合

  • 一个乐观锁很可能也是一个轻量级锁
  • 一个悲观锁也很可能是一个重量级锁

接下来我们从更深层来介绍轻量级锁和重量级锁

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

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

 即如下图所示:

 

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

 

重量级锁 : 加锁机制重度依赖了 OS 提供的 mutex
  • 大量的内核态用户态切换
  • 很容易引发线程的调度
这两个操作 , 成本比较高 . 一旦涉及到用户态和内核态的切换 , 就意味着 " 沧海桑田 ".
轻量级锁 : 加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成 . 实在搞不定了 , 再使用 mutex.
  • 少量的内核态用户态切换.
  • 不太容易引发线程调度

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

 三.自旋锁 vs 挂起等待锁

  • 自旋锁是轻量级锁的一种典型体现
  • 挂起等待锁是重量级锁的一种典型体现

按之前的方式,线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度.

但实际上, 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU. 这个时候就可以使用自旋锁来处理这样的问题.

如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来.

一旦锁被其他线程释放, 就能第一时间获取到锁

  •  自旋锁通常是用户态的,不需要经过内核态(时间相对更短)
  • 挂起等待锁通过内核的机制来实现挂起等待(时间相对更长)


那么针对上述三组策略,synchronized 这把锁属于哪种呢?

synchronized 既是悲观锁也是乐观锁,既是轻量级锁,也是重量级锁.

轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁实现

synchronized 会根据当前锁竞争的激烈程度,自适应

  • 如果锁冲突不激烈,以轻量级锁/乐观锁的状态运行
  • 如果锁冲突激烈,以重量级锁/悲观锁的状态运行

四.互斥锁 vs 读写锁

synchronized就是互斥锁,但是它的加锁就只是简单的加锁,就没有更细化的区分了

想synchronized就只有两个操作

  1. 进入代码块,加锁
  2. 出代码块,解锁

然而,除了这个之外,还有一种读写锁,能够把读和写两种加锁区分开. 

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需 要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。

读写锁(readers-writer lock),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。

 一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.

  • 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.
  • 两个线程都要写一个数据, 有线程安全问题.
  • 一个线程读另外一个线程写, 也有线程安全问题.

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock , 实现了读写锁.

  • ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行 加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进 行加锁解锁.

 其中,

  • 读加锁和读加锁之间, 不互斥.
  • 写加锁和写加锁之间, 互斥.
  • 读加锁和写加锁之间, 互斥.
只要是涉及到 " 互斥 ", 就会产生线程的挂起等待 . 一旦线程挂起 , 再次被唤醒就不知道隔了多
久了 .
因此尽可能减少 " 互斥 " 的机会 , 就是提高效率的重要途径

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

Synchronized 不是读写锁

五.可重入锁 vs不可重入锁

如果一个锁,在一个线程中,连续对该锁加锁两次,不死锁的就叫做可重入锁,如果死锁了,就叫做不可重入锁

可重入锁的字面意思是 可以重新进入的锁 ,即 允许同一个线程多次获取同一把锁
比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是 可重入 (因为这个原因可重入锁也叫做 递归锁
Java 里只要以 Reentrant 开头命名的锁都是可重入锁,而且 JDK 提供的所有现成的 Lock 实现类,包括 synchronized 关键字锁都是可重入的。
Linux 系统提供的 mutex 是不可重入锁 .

 一个线程没有释放锁, 然后又尝试再次加锁.

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

按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第

二个锁. 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无

法进行解锁操作. 这时候就会 死锁,这样的锁称为 不可重入锁.

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

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

相关文章

C++面试题准备

文章目录 一、线程1.什么是进程,线程,彼此有什么区别?2.多进程、多线程的优缺点3.什么时候用进程,什么时候用线程4.多进程、多线程同步(通讯)的方法5.父进程、子进程的关系以及区别6.什么是进程上下文、中断上下文7.一…

基于SpringBoot的视频网站系统

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 视频分享管理 视频排名管理 交流论坛管理 留言板管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 使用旧方法对视频信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运…

YOLOv8改进算法之添加CA注意力机制

1. CA注意力机制 CA(Coordinate Attention)注意力机制是一种用于加强深度学习模型对输入数据的空间结构理解的注意力机制。CA 注意力机制的核心思想是引入坐标信息,以便模型可以更好地理解不同位置之间的关系。如下图: 1. 输入特…

C++(STL容器适配器)

前言: 适配器也称配接器(adapters)在STL组件的灵活组合运用功能上,扮演着轴承、转换器的角色。 《Design Patterns》对adapter的定义如下:将一个class的接口转换为另一个class的接口,使原本因接口不兼容而…

ParagonNTFSforMac_15.5.102中文版最受欢迎的NTFS硬盘格式读取工具

Paragon NTFS for Mac是一款可以为您轻松解决Mac平台上不能识别Windows通用的NTFS文件难题,这是一款强大的Mac读写工具,相信在很多时候,Mac用户需要对NTFS文件的移动硬盘进行写入,但是macOS系统默认是不让写入的,使用小…

【gitlab】本地项目上传gitlab

需求描述 解决方法 下面的截图是gitlab空项目的描述 上传一个本地项目按其中“Push an existing folder”命令即可。 以renren-fast项目为例 # 用git bash 下载renren-fast项目 git clone https://gitee.com/renrenio/renren-fast.git# 在renren-fast的所属目录 打开git ba…

项目进展(八)-编写代码,驱动ADS1285

一、代码 根据芯片的数据手册编写部分驱动,首先看部分引脚的波形: DRDY: CS: 首先在代码初始化时连续写入三个寄存器: void WriteReg(uint8_t startAddr, uint8_t *regData, uint8_t number) {uint8_t i0;// 循环写number1次…

GEE16: 区域日均降水量计算

Precipitation 1. 区域日均降水量计算2. 降水时间序列3. 降水数据年度时间序列对比分析 1. 区域日均降水量计算 今天分析一个计算区域日均降水量的方法: 数据信息:   Climate Hazards Group InfraRed Precipitation with Station data (CHIRPS) is a…

MySQL:数据库的物理备份和恢复-冷备份(3)

介绍 物理备份: 直接复制数据文件进行的备份 优点:不需要其他的工具,直接复制就好,恢复直接复制备份文件即可 缺点:与存储引擎有关,跨平台能力较弱 逻辑备份: 从数据库中导出数据另存而进行的备…

后端面经学习自测(二)

文章目录 1、Http1.1和2.0的区别大概是什么?HTTP & HTTPS 2、HTTP,用户后续的操作,服务端如何知道属于同一个用户cookie & session & token手机验证码登录流程SSO单点登录 3、如果服务端是一个集群机器?4、hashmap是线…

Postman使用实例

Postman使用实例 实体类Emp package com.example.springboot_postman.pojo;import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import javax.persistence.*; import j…

map和set

1. 关联式容器 我们接触过STL中的部分容器,比如:vector、list、deque、 forward_list(C11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。 那什么是关联式容器?它与…

【AI视野·今日Robot 机器人论文速览 第四十二期】Wed, 27 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Wed, 27 Sep 2023 Totally 48 papers 👉上期速览✈更多精彩请移步主页 Interesting: 📚***Tactile Estimation of Extrinsic Contact,基于触觉的外部接触估计与稳定放置 (from 三菱电机) 📚充气型…

Pikachu靶场——远程命令执行漏洞(RCE)

文章目录 1. RCE1.1 exec "ping"1.1.1 源代码分析1.1.2 漏洞防御 1.2 exec "eval"1.2.1 源代码分析1.2.2 漏洞防御 1.3 RCE 漏洞防御 1. RCE RCE(remote command/code execute)概述: RCE漏洞,可以让攻击者直接向后台服务器远程注入…

【Java 进阶篇】JDBC数据库连接池Druid详解

在Java应用程序中,与数据库进行交互是一个常见的任务。为了更有效地管理数据库连接并提高性能,数据库连接池是一种常见的解决方案。Druid是一个流行的JDBC数据库连接池,它具有丰富的功能和高性能。本博客将详细介绍Druid连接池,包…

代数科学计算:LiveMath Maker v3.6.0 cRACK

LiveMath Maker 是一个数学计算机程序,您可以使用它来制作(并探索、实验和创建)LiveMath。您和LiveMath Maker制作的任何LiveMath都可以通过互联网与世界共享。 用LiveMath Maker 来进行数学研究,函数学习,深入探索、实验和创建你的LiveMath…

C++11新特性(语法糖,新容器)

距离C11版本发布已经过去那么多年了,为什么还称为新特性呢?因为笔者前面探讨的内容,除了auto,范围for这些常用的,基本上是用着C98的内容,虽说C11已经发布很多年,却是目前被使用最广泛的版本。因…

数据库的备份与恢复

数据备份的重要性 备份的主要目的是灾难恢复。 在生产环境中,数据的安全性至关重要。 任何数据的丢失都可能产生严重的后果。 造成数据丢失的原因: 程序错误人为操作错误运算错误磁盘故障灾难(如火灾、地震)和盗窃 数据库备份…

回收站里面删除的照片如何恢复?

现在拍照已经成为人们生活中的一种方式,照片为我们保留了许多珍贵而美好的回忆。大家通常会把重要的照片保存在硬盘里,但当不小心把照片移入回收站并彻底删除时,情况就有点糟糕了。那么,回收站里删除的照片还有办法恢复吗&#xf…

六、互联网技术——数据存储

文章目录 一、存储系统层次结构二、按照重要性分类三、磁盘阵列RAID三、RAID基础四、磁盘阵列分级五、数据备份与恢复六、容灾与灾难恢复 一、存储系统层次结构 常见的三层存储体系结构如下图所示,分为高速缓冲存储器、主存储器和外存储器。 二、按照重要性分类 …