【JavaEE初阶系列】——常见的锁策略

目录

🚩乐观锁和悲观锁

🚩读写锁和普通互斥锁

🚩轻量级锁和重量级锁

🚩自旋锁和挂起等待锁

🚩公平锁和非公平锁

🚩可重入锁和不可重入锁

🚩关于synchronized的锁策略以及自适应


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

🚩乐观锁和悲观锁

 这两种锁是站在加锁解锁的角度看待的,看的是加锁解锁的过程中所干的活是多还是少

悲观和乐观是对后续锁冲突是否激烈(频繁)给出的预测。

  • 如果预测接下来锁冲突的概率不大,就可以少做一些工作,就称为 乐观锁
  • 如果预测接下来锁冲突的概率很大,就应该多做一些工作,就称为 悲观锁
悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
举个例子:
同学 A 认为 " 老师是比较忙的 , 我来问问题 , 老师不一定有空解答 ". 因此同学 A 会先给老师发消息 : " 老师你忙嘛? 我下午两点能来找你问个问题嘛 ?" ( 相当于加锁操作 ) 得到肯定的答复之后 , 才会真的来问问题 .如果得到了否定的答复, 那就等一段时间 , 下次再来和老师确定时间 . 这个是悲观锁。
乐观锁: 假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并 发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。
举个例子:
同学 B 认为 " 老师是比较闲的 , 我来问问题 , 老师大概率是有空解答的 ". 因此同学 B 直接就来找老师 .( 没加锁, 直接访问资源 ) 如果老师确实比较闲 , 那么直接问题就解决了 . 如果老师这会确实很忙 , 那么同学 B也不会打扰老师, 就下次再来 ( 虽然没加锁 , 但是能识别出数据访问冲突 ). 这个是乐观锁。
这两种思路不能说谁优谁劣, 而是看当前的场景是否合适.
  • 如果当前老师确实比较忙, 那么使用悲观锁的策略更合适, 使用乐观锁会导致 "白跑很多趟", 耗费额外的资源。
  • 如果当前老师确实比较闲, 那么使用乐观锁的策略更合适, 使用悲观锁会让效率比较低.

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

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

🚩读写锁和普通互斥锁

读写锁:

  • Ⅰ:加读锁
  •  Ⅱ:加写锁

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

读写锁( readers-writer lock ),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。
  • 读锁和读锁之间不会产生竞争
  • 写锁和写锁之间会产生竞争
  • 读锁和写锁会产生竞争
一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.
  • 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.
  • 两个线程都要写一个数据, 有线程安全问题.
  • 一个线程读另外一个线程写, 也有线程安全问题.

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.
  • ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.
读写锁特别适合于 "频繁读, 不频繁写" 的场景中. (这样的场景其实也是非常广泛存在的).
比如对一个系统管理
  • 每节课老师都会进行点名,点名就需要查看班级的同学列表(读操作),这个操作可能要每周执行好几次。
  • 而什么时候修改同学列表呢?(写操作),就新同学加入的时候,可能一个月都不必改一次。
  • 再比如,同学们使用教务系统查看作业(读操作),一个班级的同学很多,读操作一天就要进行几十次上百次。
  • 但是这一节课的作业,老师只布置了一次(写操作)

普通互斥锁:就如同Synchronized,当两个线程竞争同一把锁的时候就会产生阻塞等待。synchronize不是读写锁,只是普通互斥锁。

多个线程同时读一个变量并没有问题,而且读的场景相比于写的场景就多了很多,使用读写锁相比于普通互斥锁就减少了很多的锁竞争,大大的优化了效率。(因为我们上述说明了,读写锁在读锁和读锁之间不会产生竞争)


🚩轻量级锁和重量级锁

  • 轻量级锁的加锁解锁开销比较少,典型的是纯用户态的加锁解锁逻辑,开销是比较少的
  • 重量级锁的加锁解锁开销比较大,典型的是进入了系统内核态的加锁解锁逻辑,开销是比较大的

这两种锁是站在结果的角度看待最终加锁解锁消耗的时间是多还是少,和乐观锁与悲观锁并不一样通常情况下乐观锁比较轻量,悲观锁比较重量,但是也并不绝对。


🚩自旋锁和挂起等待锁

  • 自旋锁:相当于是"忙等"的状态,大量消耗的CPU资源,反复询问当前锁是否就绪。
  • 挂起等待锁:把CPU资源空闲出来去做其他的事情,过一段时间才询问当前锁是否就绪。

自旋锁:

按之前的方式,线程在抢锁失败后进入阻塞状态,放弃 CPU ,需要过很久才能再次被调度 .
但实际上 , 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU. 这个时候就可以使用自旋锁来处理这样的问题。
自旋锁伪代码:
while (抢锁(lock) == 失败) {}
如果获取锁失败 , 立即再尝试获取锁 , 无限循环 , 直到获取到锁为止 . 第一次获取锁失败 , 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁 .

理解自旋锁 vs 挂起等待锁

举个例子:

在我们等人的时候,对方还没有到达约定的地点,一直反复的打电话催促就是自旋锁,而当你发现对方还没到的时候,就在约定的地方找个地方玩手机,叫他来了再在约定的地点旁边找我们一下,多消耗一点时间,却能够用这些时间去做其他的事情,时间被利用起来了,这就是挂起等待锁。

自旋锁是轻量级锁的一种体现,挂起等待锁是重量级锁的一种体现。



🚩公平锁和非公平锁

  • 公平锁:公平锁是先来后到,谁先来谁就拿到锁
  • 非公平锁:多个线程同时竞争一把锁,有一个线程是比较晚来的,却比其他先来的线程先拿到锁

举个例子:

  • t1,t2,t3三个线程竞争同一把锁,t1先来的,所以t1先拿到了锁,这就叫公平锁.
  • 而如果t3是晚来的,然后t3比其他两个线程先拿到了锁,这就叫非公平锁

操作系统默认的锁的调度,是非公平的情况,想要实现一个公平锁,就需要引入额外的数据结构,来记录线程加锁的顺序,需要一定的额外开销。

公平锁和非公平锁没有好坏之分 , 关键还是看适用场景 .
synchronized 是非公平锁 .

🚩可重入锁和不可重入锁

  • 可重入锁的字面意思是可以重新进入的锁,即允许同一个线程多次获取同一把锁
  • 比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入(因为这个原因可重入锁也叫做递归锁

可重入锁:同一个线程对同一把锁连续加锁两次不会造成死锁

不可重入锁:同一个线程对同一把锁连续加锁两次会造成死锁

理解 "把自己锁死"
一个线程没有释放锁, 然后又尝试再次加锁.
// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待. 
lock();
按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第二个锁. 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作. 这时候就会 死锁。
这样的锁称为 不可重入锁.
synchronized 是可重入锁 (因为我们之前在synchronized中说到了死锁,一个线程,连续针对一把锁,加锁两次,不会出现 死锁 )但是俩个线程俩把锁, 线程t1获取锁A,t2线程获取锁,B 线程t1获取锁B,t2线程获取锁A.这样大概率是会导致死锁现象。

🚩关于synchronized的锁策略以及自适应

①既是乐观锁,也是悲观锁

②既是轻量级锁,也是重量级锁

③既是自旋锁,也是挂起等待锁

④不是读写锁,是普通互斥锁

⑤是非公平锁

⑥是可重入锁

  • 初始情况下,synchronized会预测当前的锁冲突的概率不大。此时以乐观锁的模式来运行。(此时也就是轻量级锁,基于自旋锁方式实现)
  • 在实际使用过程中,如果发现锁冲突的情况比较多,synchronized就会升级成 悲观锁(也就是重量级锁,基于挂起等待的方式实现)

synchronized是自适应的,初始使用的时候,是 乐观锁/轻量级锁/自旋锁,如果竞争不激烈则保持这个状态不变,如果锁竞争激烈了,synchronized会自动升级成为 悲观锁/重量级锁/挂起等待锁,所以synchronized是"智能"的。


能“开心的话,当笨蛋也没关系".

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Springboot之RESTful风格

目录 1、概述: 1.1、传统风格的API: 1.2、RESTful风格的API: 1.3、GET、POST、PUT、DELETE: 2、RESTful风格相关的注解: ①PathVariable,用来获取url中的数据; ②GetMapping,接…

C++教学——从入门到精通 6.ASCII码与字符型

如何把小写字母转换成大写字母呢? 这个问题问的好,首先我们要新学一个类型——char 这个类型就是字符型 再来说说ASCII码 给大家举几个例子 空格————32 0————48 9————57 A————65 Z————90 a————97 z————122 我们…

Cortex-M7中断向量表的重定向

1 前言 系统上电后,PC会指向复位向量,即向量表中的Reset_Handler,而系统就是通过Vector Table Offset Register (VTOR)的值加上4字节来找到复位向量的入口的。 因为地址 0 处应该存储引导代码,所以它通常映射到 Flash 或者是 ROM …

Unity类银河恶魔城学习记录11-14 p116 Thunder strike item effect源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili ThurderStrike_Controller.cs using System.Collections; using System.C…

如何提高小红书笔记的收录率?

在小红书平台上,笔记的收录率是衡量一篇笔记是否受欢迎和有价值的重要因素。为了提高笔记的收录率,有几个关键点需要注意: 1.内容不涉及广告 在发布笔记前要先确保笔记内容不包含任何形式的广告或推广信息。小红书平台对于广告性质的内容有…

关于Ansible的模块②

转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。 接《关于Ansible的模块 ①-CSDN博客》,继续学习和梳理Ansible的常用文件类模块 1. copy模块 从当前机器上复制文件到…

关于 Unreal 的各种坐标系、输入与逻辑的转换问题

说明 已知: 在世界原点往 X 轴方向看去,ForwardVector 为 [ 1 , 0 , 0 ] [1,0,0] [1,0,0],此时的右手边的方向为 [ 0 , 1 , 0 ] [0,1,0] [0,1,0] 手柄摇杆、鼠标移动朝右得到的 [ 1 , 0 , 0 ] [1,0,0] [1,0,0],朝上得到的是…

代码随想录:字符串5-7

右旋字符串 题目 字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。 例如,对于输入字符…

货币与利率

货币与利率 货币及其职能什么是货币货币的职能货币带来了什么? 货币形式的演变商品货币代用货币信用货币货币的特性 现代社会货币的表现形式流通中的现金支票存款信用卡储存存款 货币层次划分目的划分标准划分种类我国的货币层次 货币与物价的关系利率什么是利息什么…

算法学习——LeetCode力扣补充篇1

算法学习——LeetCode力扣补充篇1 1365. 有多少小于当前数字的数字 1365. 有多少小于当前数字的数字 - 力扣(LeetCode) 描述 给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。 换而言之&a…

推荐一本牛逼的入门 Python书!,如何试出一个Python开发者真正的水平

本书详细解说了 Python 语言和编程的本质,无论你是否接触过编程语言,只要是 Python 编程的初学者,都可阅读本书。 本书讲解的内容虽然基础,但并不简单。本书提供了 165 幅图表,可以让大家能够轻松地理解并掌握复杂的概…

Taro+vue3 监听当前的页面滚动的距离

1.需求 想实现一个这样的效果 一开始这个城市组件 是透明的 在顶部 的固定定位 当屏幕滑动的时候到一定的距离 将这个固定的盒子 背景颜色变成白色 2.Taro中的滚动 Taro中的滚动 有固定的api 像生命周期一样 这个生命周期是 usePageScroll import Taro, { useDidShow, useP…

外包干了5天,技术退步明显.......

先说一下自己的情况,大专生,18年通过校招进入杭州某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

Django源码之路由的本质(上)——逐步剖析底层执行流程

目录 1. 前言 2. 路由定义 3. 路由定义整体源码分析 3.1 partial实现path函数调用 3.2 图解_path函数 3.3 最终 4.URLPattern和Pattern的简单解析 5. 小结 1. 前言 在学习Django框架的时候,我们大多时候都只会使用如何去开发项目,对其实现流程并…

【C++】位图

> 作者:დ旧言~ > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:手撕哈希表的闭散列和开散列 > 毒鸡汤: 坚持不懈,才能在困难面前看到光明的希望。 > 专栏选自:C嘎嘎进阶 >…

基于单片机病房温度监测与呼叫系统设计

**单片机设计介绍,基于单片机病房温度监测与呼叫系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机病房温度监测与呼叫系统设计概要主要涵盖了通过单片机技术实现病房温度的实时监测以及病人呼叫功能…

java字符串(一)-- 字符串API,StringBuffer 和 StringBuilder,Object

String字符串相关的类 String的特性 String类:代表字符串。Java 程序中的所有字符串字面值(如"abc" )都作为此类的实例实现。String类是引用数据类型。 在 Java 8 中,String 内部使用 char 数组存储数据。 public fi…

C++入门知识点

目录 一、命名空间 1.命名空间的定义 2.命名空间的使用 二、输入和输出 三、缺省参数 1.缺省参数的概念 2.缺省参数的分类 1)全缺省参数 2)半缺省参数 四、函数重载 五、引用 1.引用的概念 2.引用的特性 3.引用和指针的区别 六、内联函…

gan zoo: 最新GAN 相关paper/code收集

相关推荐: 简单实现 GAN 简单实现 DCGAN 简单实现 InfoGAN 简单实现 Pix2Pix 一文带你读懂概率生成模型 GPT-1/GPT-2/GPT-3简介 GPT从0到1构建(附视频代码链接) 一文带你读懂变分自编码器(VAEs) 文本引导图像生成模型的演变(DALLE/CLIP/GLIDE) 作者对迄今为止所有的…

YOLOv7--复现并训练自己的数据集(简单)

目录 1、官网下载代码 2、上传代码至服务器 3、配置环境 4、上传数据集 5、新建yaml文件 6、修改Yolov7.yaml文件 7、修改train.py文件 8、开始训练 9、复现Yolov7遇到的错误: 1、官网下载代码 Yolov7代码下载网址: https://github.com/WongKi…