程序员不可能不知道的常见锁策略

前面我们学习过线程不安全问题,我们通过给代码加锁来解决线程不安全问题,在生活中我们也知道有很多种类型的锁,同时在代码的世界当中,也对应着很多类型的锁,今天我们对锁一探究竟!

1. 常见的锁策略

注意: 接下来介绍的锁策略不仅仅是局限于Java.任何和"锁"相关的话题,都可能会涉及到以下内容.这些特性主要是给锁的实现者来参考的.
我们普通的程序猿也需要了解⼀些,对于合理的使用锁也是有很大帮助的.

1.1 乐观锁vs悲观锁

乐观锁: 在执行任务之前预期竞争不激烈,那就可以先不加锁,等后面如果真实发生了锁竞争再加锁

举个例子: 我很喜欢我的女友,在学校时,我们天天在一起,我不担心有冲突别人会占用她(并没有对她上锁),但是偶尔她会和她的好朋友出去,此时,我感觉到有人在和我竞争她,我就加锁,不让她离开我

悲观锁: 在执行任务之前预期竞争非常激烈(表示在执行任务的时候会发生锁冲突),必须先加锁再执行任务

举个例子: 我很喜欢我的女友,为了和她天天在一起,我对她上锁,这样当别人想要和她玩的时候,就会和我竞争,但是我持有锁,别人就会得不到她,显示她一直被我持有,哈哈哈

1.2 重量级锁vs轻量级锁

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

  • CPU提供了"原子操作指令".
  • 操作系统基于CPU的原子指令,实现了mutex 互斥锁.
  • JVM基于操作系统提供的互斥锁,实现了synchronizedReentrantLock 等关键字和类.
    在这里插入图片描述

轻量级锁: 加锁机制尽可能不使用mutex,而是尽量在用户态代码完成.实在搞不定了,再使用mutex.加锁的过程比较简单,用到的资源比较少,典型就是用户态的一些操作(JAVA 层面就可以完成加锁)

举个例子: 有对象的男同胞,都会遇到的问题,就是和女友出去玩的时候,会等待女友的精心打扮(化妆),轻量级锁就是不停的自旋,一会问一下“宝宝,化好妆了吗”(只是一昧的催促女友,不干别的事情,消耗资源较少),可以第一时间知道女友啥时候化好妆可以出发

重量级锁: 加锁机制重度依赖了OS提供了mutex,加锁的过程比较复杂,用到的资源比较多,典型的就是内核态的一些操作

举个例子: 当女友在化妆的时候,我们不去过问,而是做好自己的事情,准备好出去玩的所有东西(一直在帮忙做事,消耗了很多资源),等待女友的召唤(唤醒),不能第一时间知道女友啥时候化好妆

理解用户态和内核态:
想象去银行办业务.
在窗口外,自己做,这是用户态.用户态的时间成本是比较可控的.
在窗口内,工作人员做,这是内核态.内核态的时间成本是不太可控的. 如果办业务的时候反复和工作人员沟通,还需要重新排队,这时效率是很低的.

乐观锁是能不加锁就不加锁,从而导致他干活少,消耗资源也少,所以可以说乐观锁就是一种轻量级锁
悲观锁是任何时候都加锁,从而导致他干活多,消耗资源也多,所以可以说悲观锁就是一种重量级锁

1.3 自旋锁vs挂起等待锁

自旋锁: 不停的检查锁是否被释放,如果一旦锁被释放就可以直接获取锁资源
挂起等待锁: 阻塞等待,等待到被唤醒

举个例子: 每周末,我要和我对象一起吃饭,我到了她宿舍楼下打电话问她啥时候下来,她说等一会,我就不停的打电话(一直在自旋,不停的检查锁的状态,她下楼了,我会第一时间发现)——>自旋锁,我不打电话了,然后去小亭子坐下来,等待(我就不能第一时间发现她下楼如果她下楼之后就需要喊我一声相当于通知我(唤醒),获取锁资源)——>阻塞:挂起等待锁

优缺点:

  1. 自旋锁:
    纯用户态的操作,可以第一时间获取到锁,
    有自旋次数和时间的限制,通过这个限制可以控制对系统资源的消耗,可以第一时间获取到锁
  2. 挂起等待锁:
    内核态的操作,会生成对应的加锁指令,要等待唤醒,在等待的过程中会释放CPU资源

自旋锁详情请看后续CAS详细介绍

1.4 公平锁vs非公平锁

公平锁: 先来后到,先排队的线程先拿到锁,后排队的线程后拿到锁,JAVA的JUC中有一个类专门实现了公平锁
非公平锁: 大家去抢,谁先抢到是谁的,synchronized是一个非公平锁

注意:

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

举个例子: 现实生活中如果要真正的公平:立法、执法、教育、环境都要发挥作用消耗更大的资源,实现公平锁的过程也是一样,需要用额外的逻辑去管理线程,做到先来后到

1.5.读写锁(readers-writerlock)

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

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

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

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

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

其中,

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

注意,只要是涉及到"互斥",就会产⽣线程的挂起等待.⼀旦线程挂起,再次被唤醒就不知道隔了多久了.因此尽可能减少"互斥"的机会,就是提高效率的重要途径.
适用场景:
读写锁特别适合于"频繁读,不频繁写"的场景中.(这样的场景其实也是非常广泛存在的).

举个例子: 大学生必备学习通,老师会经常上课点名(读操作),发布学习资料(写操作),同学们看详细资料(读操作),读操作会涉及很多次,但是写操作偶尔几周一次,所以很适合读写锁

1.6可重入锁VS不可重入锁

可重入锁: 对一把锁可以连续加多次,多次加锁也要多次解锁,不造成死锁
不可重入锁: 对一把锁可以连续加多次,造成死锁

2. 相关面试题

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

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

2.2 介绍下读写锁?

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

2.3 什么是自旋锁,为什么要使用自旋锁策略呢,缺点是什么?

  1. 如果获取锁失败,立即再尝试获取锁,无限循环,直到获取到锁为止.第⼀次获取锁失败,第二次的尝试会在极短的时间内到来.⼀旦锁被其他线程释放,就能第⼀时间获取到锁.

相比于挂起等待锁,

  1. 优点:没有放弃CPU资源,⼀旦锁被释放就能第⼀时间获取到锁,更高效.在锁持有时间比较短的场景下非常有用.
  2. 缺点:如果锁的持有时间较长,就会浪费CPU资源.

2.4 synchronized 是可重入锁么?

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

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

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

相关文章

当设置dialog中有el-table时,并设置el-table区域的滚动,看到el-table中多了一条横线

问题:当设置dialog中有el-table时,并设置el-table区域的滚动,看到el-table中多了一条横线; 原因:el-table有一个before的伪元素作为表格的下边框下,初始的时候已设置,在滚动的时候并没有重新设置…

模型部署工具01:Docker || 用Docker打包模型 Build Once Run Anywhere

Docker 是一个开源的容器化平台,可以让开发者和运维人员轻松构建、发布和运行应用程序。Docker 的核心概念是通过容器技术隔离应用及其依赖项,使得软件在不同的环境中运行时具有一致性。无论是开发环境、测试环境,还是生产环境,Do…

2025 最新flutter面试总结

目录 1.Dart是值传递还是引用传递? 2.Flutter 是单引擎还是双引擎 3. StatelessWidget 和 StatefulWidget 在 Flutter 中有什么区别? 4.简述Dart语音特性 5. Navigator 是什么?在 Flutter 中 Routes 是什么? 6、Dart 是不是…

Flask简介与安装以及实现一个糕点店的简单流程

目录 1. Flask简介 1.1 Flask的核心特点 1.2 Flask的基本结构 1.3 Flask的常见用法 1.3.1 创建Flask应用 1.3.2 路由和视图函数 1.3.3 动态URL参数 1.3.4 使用模板 1.4 Flask的优点 1.5 总结 2. Flask 环境创建 2.1 创建虚拟环境 2.2 激活虚拟环境 1.3 安装Flask…

记一次常规的网络安全渗透测试

视频教程在我主页简介和专栏里 目录: 前言 互联网突破 第一层内网 第二层内网 总结 前言 上个月根据领导安排,需要到本市一家电视台进行网络安全评估测试。通过对内外网进行渗透测试,网络和安全设备的使用和部署情况,以及网络…

Dockerfile另一种使用普通用户启动的方式

基础镜像的Dockerfile # 使用 Debian 11.9 的最小化版本作为基础镜像 FROM debian:11.11# 维护者信息 LABEL maintainer"caibingsen" # 复制自定义的 sources.list 文件(如果有的话) COPY sources.list /etc/apt/sources.list # 创建…

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证6)

重新创建WebApi项目,安装Microsoft.AspNetCore.Authentication.JwtBearer包,将之前JwtBearer测试项目中的初始化函数,jwt配置类、token生成类全部挪到项目中。   重新编写login函数,之前测试Cookie和Session认证时用的函数适合m…

opencv在图片上添加中文汉字(c++以及python)

opencv在图片上添加中文汉字(c以及python)_c opencv绘制中文 知乎-CSDN博客 环境: ubuntu18.04 desktopopencv 3.4.15 opencv是不支持中文的。 这里C代码是采用替换原图的像素点来实现的,实现之前我们先了解一下汉字点阵字库。…

Python_CUDA入门教程学习记录

这是本人21年读书时学习CUDA基础知识保留的一些笔记,学习时的内容出处和图片来源不记得了,仅作为个人记录! CUDA编程关键术语: host : cpudevice : GPUhost memory : cpu 内存device memory : gpu onboard显存kernels : 调用CPU上…

从 Spark 到 StarRocks:实现58同城湖仓一体架构的高效转型

作者:王世发,吴艳兴等,58同城数据架构部 导读: 本文介绍了58同城在其数据探查平台中引入StarRocks的实践,旨在提升实时查询性能。在面对传统Spark和Hive架构的性能瓶颈时,58同城选择StarRocks作为加速引擎&…

【机器学习实战中阶】比特币价格预测

比特币价格预测项目介绍 比特币价格预测项目是一个非常有实用价值的机器学习项目。随着区块链技术的快速发展,越来越多的数字货币如雨后春笋般涌现,尤其是比特币作为最早的加密货币,其价格波动备受全球投资者和研究者的关注。本项目的目标是…

.Net Core微服务入门全纪录(五)——Ocelot-API网关(下)

系列文章目录 1、.Net Core微服务入门系列(一)——项目搭建 2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上) 3、.Net Core微服务入门全纪录(三)——Consul-服务注…

Linux(centos)安装 MySQL 8 数据库(图文详细教程)

前言 前几天写了个window系统下安装Mysql的博客,收到很多小伙伴私信需要Linux下安装Mysql的教程,今天这边和大家分享一下,话不多说,看教程。 一、删除以前安装的MySQL服务 一般安装程序第一步都需要清除之前的安装痕迹&#xff…

Linux——入门基本指令汇总

目录 1. ls指令2. pwd3. whoami指令4. cd指令5. clear指令6. touch指令7. mkdir指令8. rm指令9. man指令10. cp指令11. mv指令12. cat指令13. tac指令14. more指令15. less指令16. head指令17. tail指令18. date指令19. cal指令20. find指令21. which指令22. alias指令23. grep…

2024又是一年的CSDN之旅-总结过去展望未来

一、前言 一年就这样在忙忙碌碌的工作和生活中一晃而过,总结今年在CSDN上发表的博客,也有上百篇之多,首先感谢CSDN这个平台,能让我有一个地方记录工作中的点点滴滴,也在上面学到了不少知识,解决了工作中遇到…

k8s集群换IP

k8s集群搭建及节点加入时需要确定IP,但安装完成后设备移动到新环境可能出现网段更换或者IP被占用的情况,导致无法ping通节点或者无法打开原IP的服务。 解决方法为保持原有IP不更换,给网卡再加一个IP 这边使用两个ubuntu虚拟机模拟服务器和w…

前端面试题-问答篇-5万字!

1. 请描述CSS中的层叠(Cascade)和继承(Inheritance)规则,以及它们在实际开发中的应用。 在CSS中,层叠(Cascade)和继承(Inheritance)是两个关键的规则&#x…

大数据学习(37)- Flink运行时架构

&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦&#x1f91…

记一次数据库连接 bug

整个的报错如下: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Metho…

AI 编程工具—Cursor AI 对话模式详解 内嵌对话模式

AI 编程工具—Cursor AI 对话模式详解 内嵌对话模式 前面我们已经学习了Cursor 的两种工作模式,也就是Chat、Composer 更多细节可以看之前的文章 Cursor 对话模式详解 Chat、Composer 与 Normal/Agent 模式 这一节我们按一下最后一种模式,也就是内嵌对话模式 内嵌对话模式…