[多线程进阶] 常见锁策略
专栏简介: JavaEE从入门到进阶
题目来源: leetcode,牛客,剑指offer.
创作目标: 记录学习JavaEE学习历程
希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长.
学历代表过去,能力代表现在,学习能力代表未来!
目录:
1. 常见的锁策略
1.1 乐观锁 vs 悲观锁
1.2 读写锁:
1.3 重量级锁 vs 轻量级锁
1.4 自旋锁(Spin Lock)
1.5 公平锁 vs 非公平锁
1.6 可重入锁 vs 不可重入锁
1.7 相关面试题
1. 常见的锁策略
锁策略之所以被叫做策略 , 是因为它并不是一个具体的锁 , 而是一系列供锁的实现者来参考的特性 , 对普通程序猿合理的使用锁也是有很大的帮助.
1.1 乐观锁 vs 悲观锁
乐观锁:
假设数据一般情况下不会产生并发冲突 , 所以在数据进行提交更新的时候 , 才会正式对数据是否产生并发冲突进行检测 , 如果发现并发冲突了 , 则返回错误的信息 , 让用户决定如果去做.
悲观锁:
总是假设最坏的情况 , 每次拿数据的时候都认为别人会修改 , 所以每次都会上锁 , 这样别人想拿数据就会阻塞直到它拿到锁.
例如: 同学A 和同学B 想请教老师一个问题:
- 同学A 认为老师一定是比较忙的 , 因此他会先给老师法消息: "老师忙吗? 我上午10点向您请教一个问题."(相当于加锁操作)得到肯定回复后才会来 , 如果得到否定回复 , 那就等一段时间 , 下次再问老师. 这是个悲观锁
- 同学B 认为老师一定是比较闲的 , 因此他会直接去找老师(没加锁 , 直接访问资源) , 如果老师确实比较闲 , 那么问题就解决了. 如果老师比较忙 , 那么也不会打扰老师下次再来(虽然每加锁 , 但能识别出数据访问冲突). 这是个乐观锁.
这两种思路的优劣要看具体的实现场景:
- 如果当前老师确实比较忙 , 那么就适合使用悲观锁 , 使用乐观锁会导致"白跑很多趟" , 耗费额外的资源.
- 如果当前老师比较闲 , 那么就适合使用乐观锁 , 使用悲观锁锁让效率
Synchronized 初始使用乐观锁策略 , 当发生锁竞争比较频繁时 , 就会自动切换成悲观锁策略.
同学C (相当于Synchronized) , 开始认为"老师应该是比较闲的" , 有问题会直接去问老师.
但直接来找老师几次后 , 发现老师都挺忙的 , 于是下次来问老师会先发消息 , 在决定是否来问问题.
乐观锁的一个重要功能就是检测出数据是发生访问冲突 , 我们可以引入一个版本号来解决.
假设需要多线程来修改"账户余额"
设当前账户余额为 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 读写锁:
多线程之间 , 数据的读取方之间不会产生线程安全 , 但数据的写入方之间以及读者之间都需要进行互斥 , 如果两种情况下都用同一个锁 , 就会产生极大的性能损耗 , 所以读写锁因此而产生.
读写锁(readers-writer lock) , 顾名思义 , 在执行加锁操作时需要额外表明读写意图 , 读者之间并不互斥 , 而写者则要求与任何人互斥.
一个线程对数据的访问 , 主要存在两种操作: 读数据 和 写数据
- ReentrantReadWriteLock.ReadLock 类表示一个读锁 , 这个对象提供了 lock / unlock 方法进行加锁操作.
- ReentrantReadWriteLock.writeLock 类表示一个写锁 , 这个对象也提供了 lock / unlock 方法进行加锁解锁.
其中:
- 读加锁和读加锁之间 , 不互斥.
- 写加锁和写加锁之间 , 互斥.
- 读加锁和写加锁之间 . 互斥.
Tips: 只要涉及到"互斥" , 就存在线程的挂起等待 , 一但线程被挂起 , 再次调用就不知在什么时候 , 因此尽可能的减少"互斥"的机会 , 就是提高效率的重要途径.
Synchronized 不是读写锁.
1.3 重量级锁 vs 轻量级锁
锁的核心特性是 " 原子性" , 这样的机制追更溯源是 CPU 这样的硬件设备提供的.
- CPU 提供了"原子操作指令".
- 操作系统基于 CPU 的原子指令 , 实现了 mutex 互斥锁.
- JVM 基于操作系统提供的互斥锁 , 实现 synchronized 和 ReentrantLock 等关键字和类.
重量级锁: 加锁机制重度依赖 OS 提供了 mutex
- 大量的内核态用户切换
- 很容易引发线程的调度
这两个操作 , 成本比较高 , 一但涉及到用户态和内核态的切换 , 就意味着"沧海桑田".
轻量级锁: 加锁机制尽可能不使用 mutex , 而是尽量在用户态代码完成 , 实在搞不定了 , 再使用 mutex.
- 少量的用户态切换
- 不容易引发线程调度
用户态 vs 内核态
假设去银行办理业务:
在窗口外 , 自己在 ATM 机办理业务就相当于用户态 , 用户态的时间成本是比较可控的.
在窗口内 , 工作人员办理 , 就是内核态 , 内核态的时间成本不可控.
如果办理业务需要和工作人员反复沟通 , 还需要重新排队 , 这样的效率是很低的.
synchronized 开始是一个轻量级锁 , 如果锁冲突较为严重 , 就会变成重量级锁. synchronized的轻量级锁是基于自旋锁实现的 , 重量级锁是基于挂起等待锁实现的.
1.4 自旋锁(Spin Lock)
按照上文的结论 , 线程在强锁失败后会进入阻塞状态 , 放弃 CPU , 需要过很久才能再次被调度.
但实际情况下 , 虽然强锁失败 , 但过不了多久锁就会被释放 , 没必要放弃 CPU , 此时就需要自旋锁来处理这样的问题.
自旋锁伪代码:
while(抢锁(lock) == 失败){}
如果获取锁失败 , 立即再次尝试获取锁 , 知道获取锁为止 , 第一次获取锁失败 , 第二次的尝试会在极短的时间内到来.
因此 , 一但锁被其他线程释放 , 就能第一时间获取锁.
自旋锁 vs 挂起等待锁
当小明去找老师问题 , 老师说: 稍等一会 , 这会已经正在给其他同学讲题.
挂起等待锁: 回去干自己的事 , 过了很久很久之后 , 老师突然发来消息 , "这会有空闲时间"(注意 , 这个很长的时间间隔里 , 老师可能已经给多个同学讲完题了)
自旋锁: 站在老师办公室门口 , 一旦上个同学出来 , 那么就能立即抓住机会问题.
自旋锁是一种典型的 轻量级锁 的实现方式:
- 优点: 没有放弃 CPU , 不涉及线程的阻塞的调度 , 一旦锁被释放 , 就能第一时间获取到锁
- 缺点: 如果锁被其他线程占用时间过长 , 那么就会持续的消耗 CPU 资源.(挂起等待的时候不消耗 CPU 资源)
synchronized 中的轻量级锁就是通过自旋锁的方式形成.
1.5 公平锁 vs 非公平锁
假设有三个线程 A , B , C. A成功获取锁 , B和C都尝试获取锁 , 但获取失败 , 阻塞等待. 那么当 A 线程释放锁时 , 会发生什么?
公平锁: 遵循"先来后到". B 比 C 先来 , 那么当 A 释放后 , B 就可能先于 C 获得锁.
非公平锁:不遵循"先来后到". B 和 C 都有可能获得锁.
Tips:
- 操作系统内部的线程调度就是随机的 , 如果不做任何额外的限制 , 锁就是非公平锁. 如果想要实现公平锁 , 就需要依赖额外的数据结构. 记录线程的先后顺序.
- 公平锁与非公平锁没有好坏之分 , 关键在于适用场景.
synchronized 是非公平锁.
1.6 可重入锁 vs 不可重入锁
可重入锁的字面意思是 "可重新进入的锁" , 即允许一个线程多次获取同一把锁.
例如一个递归函数中有加锁操作 , 递归过程中如果锁不会阻塞自己 , 那么这个锁就是可重入锁(递归锁).
Java 中只要是以 Reentrant 开头的都是可重入锁 , 而且 JDK提供的所有现成的Lock实现类 , 包括synchronized 关键字都是可重入的.
而 LInux 系统提供的 mutex 是不可重入锁.
Synchronized 是可重入锁.
1.7 相关面试题
1. 你是怎么理解乐观锁和悲观锁的 , 具体怎么实现?
- 悲观锁认为多个线程访问同一个共享变量冲突概率较大 , 会在每次访问共享变量前真正加锁.
- 乐观锁认为多个线程访问同一个共享变量的冲突概率不大 , 并不会真的加锁 , 而是直接尝试访问数据. 在访问的同时识别当前数据是否发生访问冲突.
- 乐观锁的实现可以引入一个版本号 , 借助版本号识别当前数据是否访问冲突.
2. 介绍下读写锁?
读写锁就是把读操作和写操作分别进行加锁.
读操作和读操作之间不互斥.
读操作和写操作之间互斥.
写操作和写操作之间互斥.
读写锁的最长用场景就是 "频繁读 , 不频繁写".
3. 什么是自旋锁 , 为什么要使用自旋锁策略 , 缺点是什么?
自旋锁是一种轻量级锁 , 如果获取锁失败 , 会无限循环不停的获取锁 , 直到获取到锁为止 , 因此一但锁被其他线程释放可以第一时间获取.
相比于挂起等待锁:
优点: 没有放弃 CPU 资源 , 一但锁被释放就能第一时间获取到锁 , 更高效. 在锁持有时间比较短的情况下非常高效.
缺点: 如果锁长时间的持有机会浪费 CPU 资源.
4. synchronized 是可重入锁吗?
是可重入锁 , 可重入锁指的是连续两次加锁不会导致死锁.
实现方式是在锁中记录持有该锁的线程身份 , 以及一个计数器(记录加锁的次数) , 如果发现当前加锁的线程就是持有锁的线程 , 则直接计数器自增.
相关文章:
[多线程进阶] 常见锁策略
专栏简介: JavaEE从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录: 1. 常见的锁策略 1.1 乐观锁 vs 悲观锁 1.2 读写…...
Scala - Idea 项目报错 Cannot resolve symbol XXX
一.引言 Idea 编译 Scala 项目大面积报错 Cannot resolve symbol xxx。 二.Cannot resolve symbol xxx 1.问题描述 Idea 内的 Scala 工程打开后显示下述异常: 即 Scala 常规语法全部失效,代码出现大面积红色报错。 2.尝试解决方法 A.设置 Main Sourc…...
信息化发展与应用的新特点
一、信息化发展与应用二、国家信息化发展战略三、电子政务※四、电子商务五、两化融合(工业和信息化)六、智慧城市 一、信息化发展与应用 我国在“十三五”规划纲要中,将培育人工智能、移动智能终端、第五代移动通信(5G)先进传感器等作为新…...
软件测试】测试时间不够了,我很慌?项目马上发布了......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 常见的几种情况&…...
MapReduce编程规范
MapReduce编程规范 MapReduce的开发一共有八个步骤,其中Map阶段分为2个步骤,Shuffle阶段4个步骤,Reduce阶段分为2个步骤。 Map阶段2个步骤 设置InputFormat类,将数据切分为Key-Value(K1和V1)对,输入到第二步。 自定义Map逻辑,将第一步的结果转换成另外的…...
Unity 如何实现游戏Avatar角色头部跟随视角转动
文章目录功能简介实现步骤获取看向的位置获取头部的位置修改头部的朝向限制旋转角度超出限制范围时自动回正如何让指定动画不受影响功能简介 如图所示,当相机的视角转动时,Avatar角色的头部会同步转动,看向视角的方向。 实现步骤 获取看向的…...
深度学习优化算法总结
深度学习的优化算法 优化的目标 优化提供了一种最大程度减少深度学习损失函数的方法,但本质上,优化和深度学习的目标不同。 优化关注的是最小化目标;深度学习是在给定有限数据量的情况下寻找合适的模型。 优化算法 gradient descent…...
CMake详细使用
1、CMake简介CMake是一个用于管理源代码的跨平台构建工具可以方便地根据目标平台和编译工具产生对应的编译文件主要用于C/C语言的构建,但是也可以用于其它编程语言的源代码。如同使用make命令工具解析Makefile文件一样cmake命令工具依赖于一个CMakeLists.txt的文件该…...
【数据结构与算法】前缀树的实现
🌠作者:阿亮joy. 🎆专栏:《数据结构与算法要啸着学》 🎇座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉…...
canvas 制作2048
效果展示 对UI不满意可以自行调整,这里只是说一下游戏的逻辑,具体的API调用不做过多展示。 玩法分析 2048 的玩法非常简单,通过键盘的按下,所有的数字都向着同一个方向移动,如果出现两个相同的数字,就将…...
playwright: 全局修改页面等待超时时间
等待超时时间默认是30s, 可以通过以下几个方法设置: browser_context.set_default_navigation_timeout()browser_context.set_default_timeout()page.set_default_navigation_timeout()page.set_default_timeout() set_default_navigation_timeout set_default_n…...
C++类和对象(中)
✨个人主页: Yohifo 🎉所属专栏: C修行之路 🎊每篇一句: 图片来源 I do not believe in taking the right decision. I take a decision and make it right. 我不相信什么正确的决定。我都是先做决定,然后把…...
Docker安装EalasticSearch、Kibana,安装Elasticvue插件
使用Docker快速安装部署ES和Kibana的前提:首先需要确保已经安装了Docker环境。 如果没有安装Docker的话,先在Linux上安装Docker。 有了Docker环境后,就可以使用Docker安装部署ES和Kibana了 一、安装ES 1、拉取EalasticSearch镜像 docker p…...
算法训练营 day39 贪心算法 无重叠区间 划分字母区间 合并区间
算法训练营 day39 贪心算法 无重叠区间 划分字母区间 合并区间 无重叠区间 435. 无重叠区间 - 力扣(LeetCode) 给定一个区间的集合 intervals ,其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互…...
c/c++开发,无可避免的文件访问开发案例
一、缓存文件系统 ANSI C标准中的C语言库提供了fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等标准函数,这些函数在不同的操作系统中应该调用不同的内核API,从而支持开发者跨平台实现对文件的访问。 在Lin…...
MySQL学习笔记
MySQL学习笔记一、基础配置二、数据库操作三、表的操作1.创建表2.表选项3.查看表4.修改表5.删除表6.复制表7.检查优化修复表四、数据操作基础增删改查五、字符集编码六、数据类型(列类型)1.数值类型2.字符串类型3.日期时间类型4.枚举和集合七、列属性&am…...
ccs导入工程失败的处理方法
文章目录当导入CCS新工程时出现下述错误怎么办?方法一 从TI官网下载安装包进行安装,下载链接:软件下载完成 安装路径为上面的文件夹点击安装完成后,导入安装路径,并点击Refresh按钮,依据路径进行更新&#…...
探针台常见的故障及解决方法
症状、 可能原因、 解决方法 移动样品后画面变模糊 —显微镜不垂直,调垂直显微镜 样品台不水平 —调水平样品台 显微镜视场亮度不足,边缘切割或看不到像—转换器不在定位位置上 把转换器转到定位位置上 管镜转盘不在定位位置上 —把管镜转盘转到定…...
域内资源探测
✅作者简介:CSDN内容合伙人、信息安全专业在校大学生🏆 🔥系列专栏 :内网安全 📃新人博主 :欢迎点赞收藏关注,会回访! 💬舞台再大,你不上台,永远是…...
c# 将数据导出到EXCEL文件
第一步:项目中加入引用。 在鼠标右击项目,点击【添加】弹出菜单列表,选择【项目引用】弹出【引用管理器】对话框,选择【COM】-【Microsoft Excel 16.0 Object Library】,如图所示: 第二步,编辑…...
微服务 分片 运维管理
微服务 分片 运维管理分片分片的概念分片案例环境搭建案例改造成任务分片Dataflow类型调度代码示例运维管理事件追踪运维平台搭建步骤使用步骤分片 分片的概念 当只有一台机器的情况下,给定时任务分片四个,在机器A启动四个线程,分别处理四个…...
批量占满TEMP表空间问题处理与排查
批量占满TEMP表空间问题处理与排查应急处置问题排查查看占用TEMP表空间高的SQL获取目标SQL执行计划方法一:EXPLAIN PLAN FOR方法二:DBMS_XPLAN.DISPLAY_CURSOR方法三:DBMS_XPLAN.DISPLAY_AWR方法四:AUTOTRACE数据库跑批任务占满TE…...
Pytorch中的tensor和variable
Tensor与Variable pytorch两个基本对象:Tensor(张量)和Variable(变量) 其中,tensor不能反向传播,variable可以反向传播(forword)。 反向传播是为了让神经网络更新前面…...
暗月内网渗透实战——项目七
首先环境配置 VMware的网络配置图 环境拓扑图 开始渗透 信息收集 使用kali扫描一下靶机的IP地址 靶机IP:192.168.0.114 攻击机IP:192.168.0.109 获取到了ip地址之后,我们扫描一下靶机开放的端口 靶机开放了21,80,999,3389,5985,6588端口…...
【Java 面试合集】描述下Objec类中常用的方法(未完待续中...)
描述下Objec类中常用的方法 1. 概述 首先我们要知道Object 类是所有的对象的基类,也就是所有的方法都是可以被重写的。 那么到底哪些方法是我们常用的方法呢??? cloneequalsfinalizegetClasshashCodenotifynotifyAlltoStringw…...
SQLSERVER 的 truncate 和 delete 有区别吗?
一:背景 1. 讲故事 在面试中我相信有很多朋友会被问到 truncate 和 delete 有什么区别 ,这是一个很有意思的话题,本篇我就试着来回答一下,如果下次大家遇到这类问题,我的答案应该可以帮你成功度过吧。 二࿱…...
【C++】CC++内存管理
就是你被爱情困住了?Wake up bro! 文章目录一、C/C内存分布二、C语言中动态内存管理方式三、C中内存管理方式1.new和delete操作内置类型2.new和delete操作自定义类型(仅限vs的底层实现机制,new和delete一定要匹配使用,…...
数据预处理之图像去空白
数据预处理之图像去空白图像去空白介绍方法边缘检测阈值处理形态学图像剪切图像去空白 介绍 图像去空白是指在图像处理中去除图像中的空白区域的过程。空白区域通常是指图像中的白色或其他颜色,其不包含有用的信息。去空白的目的是为了节省存储空间、提高图像处理…...
真的麻了,别再为难软件测试员了......
前言 有不少技术友在测试群里讨论,近期的面试越来越难了,要背的八股文越来越多了,考察得越来越细,越来越底层,明摆着就是想让我们徒手造航母嘛!实在是太为难我们这些测试工程师了。 这不,为了帮大家节约时…...
2月9日,30秒知全网,精选7个热点
///货拉拉将推出同城门到门跑腿服务 据介绍,两轮电动车将成为该业务的主要运力,预计将于3月中旬全面开放骑手注册和用户人气征集活动,并根据人气和线上骑手注册情况选择落地城市,于4月正式开放服务和骑手接单 ///三菱、乐天和莱茵…...
营销型网站建设都具有哪些优势/网络营销师是做什么的
Shell脚本就是将很多条命令结合起来写在一起,通过运算以及判断来实现很多功能的文本。为什么学习shell,首先一些常规的系统管理操作,并且需要自动化地执行,如果用shell脚本把这些操作集中在一起,只需要定期的执行这个s…...
住房和城乡建设部课题网站/企业网站设计规范
引入新功能之后,用户可以在VR空间打开手机Android App,比如游戏、工具和App。这些App会漂浮在虚拟大屏幕上,可以用标准的Daydream指点控制器控制,这样一来,开发者如果制作App,没有必要开发新UI,…...
搜索网站定制公司/刷僵尸粉网站推广
在一个正则表达式中,如果要提取出多个不同的部分(子表达式项),需要用到分组功能。 在 C# 正则表达式中,Regex 成员关系如下,其中 Group 是其分组处理类。 Regex –> MatcheCollection (匹配项集合) –&g…...
济南做网站公司有哪些/武汉seo人才
[学Python]要先学什么?对于零基础的学员来说没有任何的编程基础,应该学习Python基础:计算机组成原理、Python开发环境、Python变量、流程控制语句、高级变量类型、函数应用、文件操作、面向对象编程、异常处理、模块和报、飞机大战游戏制作等…...
今日山西疫情一览表最新/北京seo工程师
来源:CSDN作者:未来的地中海原文链接:https://blog.csdn.net/qq_45687410/article/details/109735281?utm_sourceappimport scrapy # 导入scrapy# 创建爬虫类 并且继承自scrapy.Spider --> 最基础的类 另外几个各类都是继承自这个类class ProxySp…...
网站建设 康盛设计/媒体软文发稿
一、避免或简化排序 应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:1.索引中不包括一个或几个待排序的列。2.GROUP BY或ORDER BY子句中列的次序与索引的次序不一样。…...