JavaEE(系列12) -- 常见锁策略

news2024/12/25 10:14:42

目录

1. 乐观锁和悲观锁

2. 轻量级锁与重量级锁

3. 自旋锁和挂起等待锁

4. 互斥锁和读写锁

5. 可重入锁与不可重入锁

6. 死锁

6.1 死锁的必要条件 

6.2 如何避免死锁

7. 公平锁和非公平锁

8. Synchronized原理及加锁过程

8.1 Synchronized 小结

8.2 加锁工作过程 

8.2.1 偏向锁

8.2.2 轻量级锁

8.2.3 重量级锁

9. 锁优化

9.1 锁消除

9.2 锁粗化 


1. 乐观锁和悲观锁

锁的实现者,预测接下来的冲突概率(锁竞争)大还是不大,然后根据这个冲突的概率,来决定接下来应该怎么做.

乐观锁:预测接下来的冲突概率不大

悲观锁:预测接下来的冲突概率很大

通常来说~~悲观锁一般要做的工作更多一些,效率会更低一些~(并不绝对)
乐观锁做的工作会更少一点,效率更高一点.

2. 轻量级锁与重量级锁

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


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


一个乐观锁很可能也是一个轻量级锁(不绝对)
一个悲观锁很可能也是一个重量级锁(不绝对)

3. 自旋锁和挂起等待锁

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

举例:追女朋友

自旋锁:小白像一个女孩表白失败,被拒绝(因为这女生有男朋友(被加锁成功)).但是一直没有放弃,每天都在追求,时刻不停歇.(这就是一个不停的等待锁的过程,)当这个女生分手了(解锁),小白就会第一时间得到通知,竞争到锁的机会就很大.

挂起等待锁:小白选择了另一种方式,小白暂时先不去追这个女生,等过一段时间来问问女生是否分手(解锁),此时就是不能立马得到通知,很有可能之前的锁解除了,但是小白没有拿到,被别人拿到了.优点就是这段时间小白不用每天都问是否分手,可以集中注意力做一些别的事情,比如学习Java.哈哈哈.

补充:

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

 synchronized既是悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁,轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁实现~~ 

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

4. 互斥锁和读写锁

synchronized,是互斥锁~~ 加锁,就只是单纯的加锁,没有更细化的区分了~~


像synchronized只有两个操作:

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

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

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

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

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

读写锁约定:

  1. 读加锁和读加锁之间, 不互斥.(没锁竞争)
  2. 写加锁和写加锁之间, 互斥.(有锁竞争)
  3. 读加锁和写加锁之间, 互斥. (有锁竞争)

生活场景应用

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

比如教务系统 

5. 可重入锁与不可重入锁

可重入锁.:如果一个锁在一个线程中,连续对该锁咔咔加锁两次不死锁,就叫做可重入锁.即允许同一个线程多次获取同一把锁。

不可重入锁:如果死锁了,就叫不可重入锁~~

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

6. 死锁

        死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

 

死锁的情况:

1. 一个线程,一把锁,可重入锁不会发生死锁,不可重入锁发生死锁

2. 两个线程,即使是可重入锁也有可能会发生死锁

3. N个线程M把锁就非常容易死锁.

6.1 死锁的必要条件 

 死锁的必要条件:(缺一不可)

  • 1.互斥使用.一个线程拿到一把锁之后,另一个线程不能使用.(锁的基本特点)
  • 2.不可抢占.一个线程拿到锁,只能自己主动释放,不能是被其他线程强行占有~~[挖墙脚是不行滴](锁的基本特点)
  • 3.请求和保持."吃着碗里的,惦记锅里的”追到了1号女神之后,又对2号女神跃跃欲试,但是此时是绝不会放弃1号女神的~~(代码的特点)
  • 4.循环等待.上面例子中的情况,逻辑依赖循环的.“钥匙锁车里了,车钥匙锁家里了”(代码的特点)

6.2 如何避免死锁

死锁是个比较严重的bug.实践中如何避免出现死锁呢?


一个简单有效的办法:破解循环等待这个条件~~
针对锁进行编号,如果需要同时获取多把锁,约定加锁顺序,务必是先对小的编号加锁后对大的编号加锁~~

如果此时约定,先加锁小的编号,后加锁大的编号,此时只要所有线程都遵守这个顺序就行了!

7. 公平锁和非公平锁

        假设三个线程 A, B, C.  A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后C 也尝试获取锁, C 也获取失败, 也阻塞等待.当线程 A 释放锁的时候, 会发生啥呢?

公平锁: 遵守 "先来后到". B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.
非公平锁: 不遵守 "先来后到". B 和 C 都有可能获取到锁.

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

synchronized 是非公平锁.

8. Synchronized原理及加锁过程

8.1 Synchronized 小结

 8.2 加锁工作过程 

                   

8.2.1 偏向锁

1) 偏向锁
        第一个尝试加锁的线程, 优先进入偏向锁状态.偏向锁不是真的 "加锁", 只是给对象头中做一个 "偏向锁的标记", 记录这个锁属于哪个线程.如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销),如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态.偏向锁本质上相当于 "延迟加锁" . 能不加锁就不加锁, 尽量来避免不必要的加锁开销.但是该做的标记还是得做的, 否则无法区分何时需要真正加锁.

8.2.2 轻量级锁

2) 轻量级锁
随着其他线程进入竞争, 偏向锁状态被消除, 进入轻量级锁状态(自适应的自旋锁).

此处的轻量级锁就是通过 CAS 来实现.(后续会总结这个CAS(先理解为比较内存和寄存器的值,相同就更新忙不相同就修改为内存的值之后再对数据进行操作))

  • 通过 CAS 检查并更新一块内存 (比如 null => 该线程引用)
  • 如果更新成功, 则认为加锁成功
  • 如果更新失败, 则认为锁被占用, 继续自旋式的等待(并不放弃 CPU).

自旋操作是一直让 CPU 空转, 比较浪费 CPU 资源.
因此此处的自旋不会一直持续进行, 而是达到一定的时间/重试次数, 就不再自旋了.也就是所谓的 "自适应"

8.2.3 重量级锁

3) 重量级锁
如果竞争进一步激烈, 自旋不能快速获取到锁状态, 就会膨胀为重量级锁

此处的重量级锁就是指用到内核提供的 mutex .

  • 执行加锁操作, 先进入内核态.
  • 在内核态判定当前锁是否已经被占用
  • 如果该锁没有占用, 则加锁成功, 并切换回用户态.
  • 如果该锁被占用, 则加锁失败. 此时线程进入锁的等待队列, 挂起. 等待被操作系统唤醒.
  • 经历了一系列的沧海桑田, 这个锁被其他线程释放了, 操作系统也想起了这个挂起的线程, 于是唤醒这个线程, 尝试重新获取锁.

9. 锁优化

9.1 锁消除

编译器+JVM 判断锁是否可消除. 如果可以, 就直接消除.


什么是 "锁消除"
有些应用程序的代码中, 用到了 synchronized, 但其实没有在多线程环境下. (例如 StringBuffer))

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");

此时每个 append 的调用都会涉及加锁和解锁. 但如果只是在单线程中执行这个代码, 那么这些加
锁解锁操作是没有必要的, 白白浪费了一些资源开销.

补充:StringBuffer相对于StringBuilder是相对线程安全的,因为StringBuffer把关键方法都加上了Synchronized关键字,但是不是绝对线程安全,看代码怎么写.

9.2 锁粗化 

锁粗化
一段逻辑中如果出现多次加锁解锁, 编译器 + JVM 会自动进行锁的粗化. 

举例:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL保证主备一致,如何解决循环复制?

备库只读,是如何和主库同步数据的? 你可能会问,我把备库设置成只读了,还怎么跟主库保持同步更新呢? 这个问题,你不用担心。因为 readonly 设置对超级 (super) 权限用户是无效的,而用于同步更新…

用Typescript 的方式封装Vue3的表单绑定,支持防抖等功能。

Vue3 的父子组件传值、绑定表单数据、UI库的二次封装、防抖等,想来大家都很熟悉了,本篇介绍一种使用 Typescript 的方式进行统一的封装的方法。 基础使用方法 Vue3对于表单的绑定提供了一种简单的方式:v-model。对于使用者来说非常方便&…

【011】C++选择控制语句 if 和 switch 详解

C控制语句之if和switch语句 引言一、选择控制语句if1.1、if 语句的形式1.2、if...else...语句的形式1.3、if...else if... else...语句 二、选择控制语句switch2.1、switch语句形式 三、switch和if...else if...else...比较四、注意事项总结 引言 💡 作者简介&#…

企业工程行业管理系统源码-专业的工程管理软件-提供一站式服务

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下: 首页 工作台:待办工作、消息通知、预警信息,点击可进入相应的列表 项目进度图表:选择(总体或单个)项目显示1…

Doo Prime 德璞资本:期货开户条件全解析!让你不再困惑!

期货市场是金融市场中一个非常重要的部分,对于许多投资者来说,期货市场是一个非常有吸引力的投资选择。然而,要进行期货交易,必须首先开设期货账户,这就需要满足一些期货开户条件,因此本文将介绍期货开户条…

认识SpringCloud(一) 注册中心Eureka

Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理。在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服…

(原创)getX+Dio实现Flutter悬浮置顶的页面效果

前言 Flutter的开发相对已经比较成熟了,现在市面上不少商业应用也在使用这个技术 老实说,Flutter去实现一些基础的ui界面,效率还是很高的 当然前提是你对它要有一定的了解。 今天就演示一下,如何去实现一个基础悬浮置顶的页面效果…

OSTrack 中的边界框回归策略

目录 一、裁剪和标签的设置 二、模型的预测输出的边界框回归 一、裁剪和标签的设置 1、添加偏移量,得到偏移后的边界框 jittered_anno [self._get_jittered_box(a, s) for a in data[s _anno]] 2、以偏移后的边界框为中心,进行裁剪 首先以偏移边界…

Apache Pulsar入门指南

1.概述 Apache Pulsar 是灵活的发布-订阅消息系统(Flexible Pub/Sub messaging),采用计算与存储分离的架构。雅虎在 2013 年开始开发 Pulsar ,于 2016 年首次开源,目前是 Apache 软件基金会的顶级项目。Pulsar 具有支…

面试官:写一个单例模式

1. 什么是单例模式 了解单例模式之前,我们需要先了解什么是设计模式。 设计模式是一种抽象的编程思想,不局限于编程语言,简单来说,就是一些大佬程序猿针对一些典型的场景,给出一些典型的解决方案,只要按照这…

04-CSS3-渐变色、2D转换、3D转换

一、渐变色 CSS渐变色(Gradient)是指在元素背景中使用两种或多种不同的颜色进行过渡,超过两个颜色可以形成更为细腻的渐变效果。常见的CSS渐变色有线性渐变和径向渐变。 1. 线性渐变:Linear Gradients 向下/向上/向左/向右/对角…

SVN 修改URL路径-使用重新定位(relocate)命令和找不到问题解决

当svn服务器url发生变更,又不想在本地重新进行checkout操作,这时候可以使用svn relocate命令进行url的重新定位; 在windows下以TortoiseSVN为例,在仓库文件夹上右键,TortoiseSVN-(重新定位)relocate, 1、Windows TortoiseSVN客户端: 在工作复本的根目录上右键->TortoiseSV…

DOUBLETROUBLE: 1

文章目录 DOUBLETROUBLE: 1实战演练一、前期准备1、相关信息 二、信息收集1、nmap探测目标靶机端口2、扫描目标网址目录3、访问网站,发现secret下有个图片4、将图片下载5、查看图片所含内容6、破解密码并查看7、登陆邮箱8、创建反弹shell9、上传反弹shell10、监听11…

Jeecg-Boot 未授权SQL注入漏洞(CVE-2023-1454)

本文转载于:https://blog.csdn.net/qq_27536045/article/details/129944987 环境搭建 JDK: 1.8 (小于11) Maven: 3.5 MySql: 5.7 Redis: 3.2 Node Js: 10.0 Npm: 5.6.0 Yarn: 1.21.1 下载源码 后端源码 https://github.com/jeecgboot/jeecg-boot/tree/v…

MongoDB安装教程—Ubuntu

为啥用MongoDB,问就是客户要求。 为啥用Ubuntu,问就是客户只有Ubuntu的机器。 0. 环境 操作系统: Ubuntu 22.04.1 LTS (GNU/Linux 5.19.0-41-generic x86_64) 不同版本系统差异不同,其他版本系统未测试。 1. 安装 1.1 包管理公…

深入探索SDL游戏开发

前言 欢迎来到小K的SDL专栏第二小节,本节将为大家带来基本窗口构成、渲染器、基本图形绘制、贴图、事件处理等的详细讲解,看完后希望对你有收获 文章目录 前言一、简单窗口二、渲染器三、基本图形绘制1、点2、线3、矩形4、圆和椭圆 四、贴图五、事件处理…

XR交互技术趋势:6DoF追踪、手势识别、眼动跟踪……

XR交互技术提供了用户与虚拟环境进行交互的方式和手段,而实时云渲染则提供了真三维、可交互、高沉浸的图形渲染和计算能力。结合这两者,用户可以通过XR设备获得更真实、更沉浸的虚拟体验,同时享受到优质的图形效果和流畅的交互响应。本篇文章…

关于开发中对端口(port)的几点理解

一、服务端的端口是固定的,客户端的端口是随机的 客户端端口是随机的,比如访问百度,系统为浏览器分配了个端口1024。过一会重开电脑,访问了新浪,可能还是用1024端口,我不关浏览器,还要再开一个浏…

CenterFusion数据处理函数__getitem__()解析

CenterFusion数据处理函数__getitem__解析 1. 图像数据处理1.1 通过利用nuScence_COCO实例化对象获取图像以及相关数据的信息1.2 获取图像数据增强的相关参数:中心点c,尺度scale,旋转rotia和翻转flip1.3 根据生成的参数生成仿射矩阵来对图像进…

spring boot 集成 swagger3

Swagger 3是一种开源的API描述工具,它可以帮助开发人员设计、构建、文档化和测试API。Swagger 3支持多种编程语言和框架,包括Java、Node.js、Python、Ruby等,并提供了许多集成工具和插件,例如Postman、Apigee等。 Swagger 3使用Op…