【多线程】常见锁策略详解(面试常考题型)

目录

  • 🌴 乐观锁 vs 悲观锁
  • 🎍重量级锁 vs 轻量级锁
  • 🍀自旋锁(Spin Lock)
  • 🎋公平锁 vs ⾮公平锁
  • 🌳可重⼊锁 vs 不可重⼊锁
  • 🎄读写锁
  • ⭕相关面试题

常⻅的锁策略

注意: 接下来讲解的锁策略不仅仅是局限于 Java . 任何和 “锁” 相关的话题, 都可能会涉及到以下内容.
这些特性主要是给锁的实现者来参考的.

普通的程序猿也需要了解⼀些, 对于合理的使⽤锁也是有很⼤帮助的.

🌴 乐观锁 vs 悲观锁

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

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

乐观锁的一个重要功能就是要检测出数据是否发生访问冲突.

那我们具体是怎么检测的呢?这里我们我们可以引入一个 “版本号” 来解决.

那什么是版本号呢?请看下面的例子:
假设我们需要多线程修改 “用户账户余额”.

设当前余额为 100. 引入一个版本号 version, 初始值为 1. 并且我们规定 "提交版本必须大于记录当前版本”才能执行更新余额

接下来我们进行以下操作:

第一步:线程 A 此时准备将其读出( version=1, balance=100 ),线程 B 也读入此信息( version=1,balance=100 )
在这里插入图片描述第二步:线程 A 操作的过程中并从其帐户余额中扣除 50( 100-50 ),线程 B 从其帐户余额中扣除 20( 100-20 );
在这里插入图片描述
第三步:线程 A 完成修改工作,将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=50),写回到内存中;
在这里插入图片描述
第四步:线程 B 完成了操作,也将版本号加1( version=2 )试图向内存中提交数据( balance=80),但此时比对版本发现,操作员 B 提交的数据版本号为 2 ,数据库记录的当前版本也为 2 ,不满足 “提交版本必须大于记录当前版本才能执行更新“ 的乐观锁策略。就认为这次操作失败.
在这里插入图片描述

在Java中,Synchronized 初始使⽤乐观锁策略. 当发现锁竞争⽐较频繁的时候, 就会⾃动切换成悲观锁策略.

🎍重量级锁 vs 轻量级锁

锁的核⼼特性 “原⼦性”, 这样的机制追根溯源是 CPU 这样的硬件设备提供的.
• CPU 提供了 “原⼦操作指令”.
• 操作系统基于 CPU 的原⼦指令, 实现了 mutex 互斥锁.
• JVM 基于操作系统提供的互斥锁, 实现了 synchronized 和 ReentrantLock 等关键字和类.

在这里插入图片描述
注意, synchronized 并不仅仅是对 mutex 进⾏封装, 在 synchronized 内部还做了很多其
他的⼯作

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

• ⼤量的内核态⽤⼾态切换
• 很容易引发线程的调度

这两个操作, 成本⽐较⾼. ⼀旦涉及到⽤⼾态和内核态的切换, 就意味着 “沧海桑⽥”.

轻量级锁: 加锁机制尽可能不使⽤ mutex, ⽽是尽量在⽤⼾态代码完成. 实在搞不定了, 再使⽤ mutex.

• 少量的内核态⽤⼾态切换.
• 不太容易引发线程调度.

什么是用户态什么是内核态

理解⽤⼾态 vs 内核态 想象去银⾏办业务. 在窗⼝外, ⾃⼰做, 这是⽤⼾态. ⽤⼾态的时间成本是⽐较可控的. 在窗⼝内, ⼯作⼈员做,
这是内核态. 内核态的时间成本是不太可控的. 如果办业务的时候反复和⼯作⼈员沟通, 还需要重新排队, 这时效率是很低的.

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

🍀自旋锁(Spin Lock)

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

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

⾃旋锁伪代码:

while (抢锁(lock) == 失败) {}

如果获取锁失败, ⽴即再尝试获取锁, ⽆限循环, 直到获取到锁为⽌. 第⼀次获取锁失败, 第⼆次的尝试会在极短的时间内到来.

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

理解⾃旋锁 vs 挂起等待锁
想象⼀下, 去追求⼀个⼥神. 当男⽣向⼥神表⽩后, ⼥神说: 你是个好⼈, 但是我有男朋友了~~
挂起等待锁: 陷⼊沉沦不能⾃拔… 过了很久很久之后, 突然⼥神发来消息, “咱俩要不试试?” (注意, 这
个很⻓的时间间隔⾥, ⼥神可能已经换了好⼏个男票了).
⾃旋锁: 死⽪赖脸坚韧不拔. 仍然每天持续的和⼥神说早安晚安. ⼀旦⼥神和上⼀任分⼿, 那么就能⽴刻
抓住机会上位.

==⾃旋锁是⼀种典型的 轻量级锁 的实现⽅式.
优点: 没有放弃 CPU, 不涉及线程阻塞和调度, ⼀旦锁被释放, 就能第⼀时间获取到锁.
缺点: 如果锁被其他线程持有的时间⽐较久, 那么就会持续的消耗 CPU 资源. (⽽挂起等待的时候是不消耗 CPU 的).

synchronized 中的轻量级锁策略⼤概率就是通过⾃旋锁的⽅式实现的.

🎋公平锁 vs ⾮公平锁

假设三个线程 A, B, C. A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后 C
也尝试获取锁, C 也获取失败, 也阻塞等待.

当线程 A 释放锁的时候, 会发⽣啥呢?

公平锁: 遵守 “先来后到”. B ⽐ C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.

⾮公平锁: 不遵守 “先来后到”. B 和 C 都有可能获取到锁.

这就好⽐⼀群男⽣追同⼀个⼥神. 当⼥神和前任分⼿之后, 先来追⼥神的男⽣上位, 这就是公平锁; 如果
是⼥神不按先后顺序挑⼀个⾃⼰看的顺眼的, 就是⾮公平锁.
在这里插入图片描述

注意:

• 操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是⾮公平锁. 如果要
想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.

• 公平锁和⾮公平锁没有好坏之分, 关键还是看适⽤场景.

synchronized 是⾮公平锁.

🌳可重⼊锁 vs 不可重⼊锁

可重⼊锁的字⾯意思是“可以重新进⼊的锁”,即允许同⼀个线程多次获取同⼀把锁

⽐如⼀个递归函数⾥有加锁操作,递归过程中这个锁会阻塞⾃⼰吗?如果不会,那么这个锁就是可重⼊锁(因为这个原因可重⼊锁也叫做递归锁)。

Java⾥只要以Reentrant开头命名的锁都是可重⼊锁,⽽且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重⼊的

⽽ Linux 系统提供的 mutex 是不可重⼊锁.

理解 “把⾃⼰锁死”
⼀个线程没有释放锁, 然后⼜尝试再次加锁.

1 // 第⼀次加锁, 加锁成功
2 lock();
3 // 第⼆次加锁, 锁已经被占⽤, 阻塞等待. 
4 lock();

按照之前对于锁的设定, 第⼆次加锁的时候, 就会阻塞等待. 直到第⼀次的锁被释放, 才能获取到第⼆个
锁. 但是释放第⼀个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想⼲了, 也就⽆法进⾏解
锁操作. 这时候就会 死锁.

在这里插入图片描述
这样的锁称为 不可重⼊锁.

最后,要记得

synchronized 是可重入锁

🎄读写锁

多线程之间,数据的读取⽅之间不会产⽣线程安全问题,但数据的写⼊⽅互相之间以及和读者之间都
需要进⾏互斥。如果两种场景下都⽤同⼀个锁,就会产⽣极⼤的性能损耗。所以读写锁因此⽽产⽣。

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

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

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

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

  • ReentrantReadWriteLock.ReadLock 类表⽰⼀个读锁. 这个对象提供了 lock / unlock ⽅法
    进⾏加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表⽰⼀个写锁. 这个对象也提供了 lock / unlock
    ⽅法进⾏加锁解锁

其中,

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

注意, 只要是涉及到 “互斥”, 就会产⽣线程的挂起等待. ⼀旦线程挂起, 再次被唤醒就不知道隔了多久
了.
因此尽可能减少 “互斥” 的机会, 就是提⾼效率的重要途径

读写锁特别适合于 “频繁读, 不频繁写” 的场景中. (这样的场景其实也是⾮常⼴泛存在的).

⽐如学校的教务系统.
每节课⽼师都要使⽤教务系统点名, 点名就需要查看班级的同学列表(读操作). 这个操作可能要每周执
⾏好⼏次.
⽽什么时候修改同学列表呢(写操作)? 就新同学加⼊的时候. 可能⼀个⽉都不必改⼀次.
再⽐如, 同学们使⽤教务系统查看作业(读操作), ⼀个班级的同学很多, 读操作⼀天就要进⾏⼏⼗次上
百次.
但是这⼀节课的作业, ⽼师只是布置了⼀次(写操作)

Synchronized 不是读写锁.、

⭕相关面试题

  1. 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?

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

2.介绍下读写锁?

读写锁就是把读操作和写操作分别进⾏加锁.
读锁和读锁之间不互斥
写锁和写锁之间互斥.
写锁和读锁之间互斥.
读写锁最主要⽤在 “频繁读, 不频繁写” 的场景中.、

3.什么是⾃旋锁,为什么要使⽤⾃旋锁策略呢,缺点是什么?

如果获取锁失败, ⽴即再尝试获取锁, ⽆限循环, 直到获取到锁为⽌. 第⼀次获取锁失败, 第⼆次的尝试
会在极短的时间内到来. ⼀旦锁被其他线程释放, 就能第⼀时间获取到锁.
相⽐于挂起等待锁,
优点: 没有放弃 CPU 资源, ⼀旦锁被释放就能第⼀时间获取到锁, 更⾼效. 在锁持有时间⽐较短的场景
下⾮常有⽤.
缺点: 如果锁的持有时间较⻓, 就会浪费 CPU 资源.

4.synchronized 是可重⼊锁么?

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

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

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

相关文章

安捷伦N5182A信号源 AgilentN5182A

描述: 1)信号特性: 250 kHz to 3 or 6 GHz频率范围 (可选低至 100 kHz) 13 dBm 1GHz输出功率 5dBm输出功率时W-CDMA动态范围:单载波 ≤-73 dBc ;4载波≤-66 dBc ≤1.2 ms切换速度在SCPI模式 2)调制与扫描&#x…

Matlab 机器人工具箱 例程:运动学+动力学+路径规划+可视化

文章目录 1 创建机器人2 机器人显示3 机器人示教4 机器人路径规划:给定关节角路径5 机器人路径规划:给定末端位姿,求关节角路径6 工作空间可视化参考链接 1 创建机器人 clc;clear;close all; deg pi/180;L1 Revolute(d, 0, a, 0, alpha, 0,…

Lazada本土店与跨境店区别,附店铺防关联攻略

许多新手商家在初入跨境电商时,对于平台账号类别并不清楚。Lazada是最大的东南亚在线购物平台之一,如果你的跨境目标正指向东南亚,那么Lazada一定是是你的首选平台。那么接下来让小编带大家认识Lazada本土店与跨境店的区别! 一、本…

部署运维 防火墙,进程 常用命令

防火墙: 1. 查看是否安装了firewalld sudo systemctl status firewalld 查看防火墙状态或者sudo systemctl is-active firewalld 查看防火墙是否是开启状态 2. 开放6379port sudo firewall-cmd --add-port6379/tcp --permanent 刷新防火墙 sudo firewall-cmd --reload 3…

经验分享:水牛社怎么做?

本人也就是通过他慢慢学习成长起来的。还是一个网友推荐的,现在他对我来说算是大佬了,已经单飞了,好久都没有联系了,呵呵,真是人往高处走,水往低处流啊。 做网赚会经常和一些网络小白聊天,聊着…

使用Matlab计算IGRAv2探空站的Tm和PWV

1. 探空站IGRAv2数据 探空站的Tm常作为真值,去检验Tm线性公式或者ERA5 Tm等的精度 。 探空站PWV常作为真值,去检验GNSS PWV等的精度 2. Tm 的计算方法 Tm 的计算方法有两种在前面的文章有讲,这里用 使用水汽压和温度计算Tm。 ei和 Ti 表示…

【OpenCV】仿 IOS 锁屏时钟

OpenCV 是一个开源的计算机视觉(Computer Vision)与机器学习软件库,提供了多种图像处理算法与接口。在图像处理中,按位运算是一类重要的运算,可以用于提取图像的重要部分。本文主要记录如何使用 OpenCV-Python 绘制仿 …

docker的简单使用

在一些进行使用靶场或者工具的时候,我们可以用docker在线拉取,就可以省去手动搭建靶场的过程 一、docker的配置 因为docker是默认从docker的官网进行拉取,所以拉取经常速度很慢或者失败,我们先要进行一下配置,让他优…

安卓手机如何使用JuiceSSH实现公网远程连接本地Linux服务器

文章目录 1. Linux安装cpolar2. 创建公网SSH连接地址3. JuiceSSH公网远程连接4. 固定连接SSH公网地址5. SSH固定地址连接测试 处于内网的虚拟机如何被外网访问呢?如何手机就能访问虚拟机呢? cpolarJuiceSSH 实现手机端远程连接Linux虚拟机(内网穿透,手机端连接Linux虚拟机) …

Frida-Hook-Native层操作大全

前期准备 使用 jadx 进行逆向工程的基础知识。能够理解 Java 代码。能够编写简短的 JavaScript 代码片段。熟悉 adb。已 root 的设备。对 x86/ARM64 汇编和逆向工程有基础了解。 1、Hook Native层中调用的函数并且读取传入的参数 对于Native层的函数Hook,我们使用…

2024.3.7

1、基于UDP的网络聊天室 服务器&#xff1a; #include<myhead.h>//定义一个结构体存储成员信息 typedef struct Node {struct sockaddr_in cin;struct Node *next; }*Linklist;//定义一个结构体表示消息类型 typedef struct _msg {char code; //L:登录&#xff0c;C…

vue3+element plus 实现百度地图显示路径

添加依赖 <!-- index.html --><script type"text/javascript" src"//api.map.baidu.com/getscript?v3.0&akyI6kBeC9G4LntEWXklE2iNHwRUrmFEQc"></script><script type"text/javascript" src"//api.map.baidu.co…

利用Python爬取高德地图全国地铁站点信息

利用Python中的requests库进行地铁站点信息的获取,同时将数据保存在本机excel中 # 首先引入所需要的包 import requests from bs4 import BeautifulSoup import pandas as pd import json# 发送 GET 请求获取网页内容 url http://map.amap.com/subway/index.html response r…

【算法沉淀】刷题笔记:并查集 带权并查集+实战讲解

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

ospf虚链路实验简述

1、ospf虚链路实验简述 ospf虚链路配置 为解决普通区域不在骨干区域旁&#xff0c;通过配置Vlink-peer实现不同区域网络设备之间建立逻辑上的连接。 实验拓扑图 r1: sys sysname r1 undo info enable int loopb 0 ip add 1.1.1.1 32 ip add 200.200.200.200 32 quit int e0/0/…

模拟实现std::string类(包含完整、分文件程序)

std库中的string是一个类&#xff0c;对string的模拟实现&#xff0c;既可以复习类的特性&#xff0c;也可以加深对std::string的理解。 &#x1f308;一、搭建框架 ☀️1.新命名空间 本质上string是一个储存在库std里面的类&#xff0c;现在需要模拟实现一个string类&#…

python基础——入门必备知识

&#x1f4dd;前言&#xff1a; 本文为专栏python入门基础的第一篇&#xff0c;主要带大家先初步学习一下python中的一些基本知识&#xff0c;认识&#xff0c;了解一下python中的一些专有名词&#xff0c;为日后的学习打下良好的基础,。本文主要讲解以下的python中的基本语法&…

力扣706:设计哈希映射

题目&#xff1a; 不使用任何内建的哈希表库设计一个哈希映射&#xff08;HashMap&#xff09;。 实现 MyHashMap 类&#xff1a; MyHashMap() 用空映射初始化对象void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中&#x…

tcpdump使用pcap-filter抓Vxlan包内数据

目录 1. 背景2. 参考3. 概念4. 环境5. 用法5.1 抓vxlan通讯中的icmp包5.2 tcpdump抓包命令解析5.2.1 tcpdump命令说明5.2.2 Vxlan协议报文解析 5.3 其他抓包例子5.3.1 抓包示例15.3.2 抓包示例2 1. 背景 看vxlan协议时&#xff0c;发现可以使用tcpdump高级用法&#xff08;pca…

Unity Samples和帧动画的问题

拖动序列帧图片和自己创建clip的帧率不同 我今天在创建帧动画的时候用了两种方式第一种是直接拖动序列帧图片到Hierachy&#xff0c;然后生成的第二种是这样我发现两者播放的动画速率不一样最后查了半天查不到原因。最后发现是Samples的原因&#xff0c;而且Unity把Samples这个…