[Java EE] 多线程(七): 锁策略

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (90平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(93平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

1.几种不同的锁策略

常见的锁策略不仅仅是局限于Java中,任何语言都适用.

1.1 乐观锁vs悲观锁

  • 乐观锁:加锁的时候,预测锁冲突的概率比较小,所以在接下来的时间里做的事情就比较少,也就是加锁的开销就比较小.但是具体是怎么判断加锁之后锁冲突的概率的,这就和JVM的源代码有关联了.
  • 悲观锁:加锁的时候,预测锁冲突的概率比较大,所以在接下来的时间里做的事情就比较多,也就是加锁的开销就比较大.

举例说明:找老师问问题
现在有A和B两个同学:同学A采用悲观锁的策略,同学B采用乐观锁的策略
同学A就会想:"我去了老师办公室之后,老师也不一定有时间回答."所以就会给老师提前发微信,询问老师是否有时间得到肯定答复之后才会来问问题.
同学B就会想:"我去了之后老师肯定有时间回答."所以就会直接找老师帮忙,如果老师比较闲,那么问题便得已解决,如果老师比较忙,也不会打扰到老师.

什么时候使用乐观锁策略,什么时候使用悲观锁策略,还是要看具体的场景.
就如上述的例子,如果老师确实比较忙,使用悲观锁就是很好的策略,如果使用乐观锁,就是白跑一趟.
老师确实比较闲的时候,使用乐观锁就是很好的策略,如果使用悲观锁,就会额外消耗加锁的开销.

1.2 重量级锁vs轻量级锁

一般来说,轻量级锁对应的就是乐观锁,重量级锁对应的就是悲观锁,两组概念经常被混起来用.
我们现在来解释一下,重量级锁究竟比轻量级锁多做了哪些事情,就是轻量级锁值通过用户态代码来加锁,而重量级锁就是通过系统内核提供的mutex来加锁,也就是通过内核态实现加锁.
在这里插入图片描述

1.3 自旋锁vs挂起等待锁

自旋锁是轻量级锁的典型实现方式,挂起等待锁是重量级锁的典型实现方式.

  • 自旋锁: 按之前的方式,线程抢占锁失败就会进入阻塞等待状态,放弃CPU.
    但实际上,大部分情况下,虽然当前抢占锁失败,但是过了不久之后,锁就会被立刻释放掉,现在就没必要放弃CPU,这时候就可以使用自旋锁的方式来解决上述问题
void locker(){
	while(true){
		if(锁是否被占用){
			continue;
		}
		获取到锁;
		break;
	}
}

从上述伪代码中,我们可以看到自旋锁采用了忙等的策略,如果获取锁失败,就立即再尝试获取锁,无限循环,直到获取到锁为止.
这种锁的优点就是:没有放弃CPU,不涉及阻塞和调度,一旦锁被释放,就可以第一时间获取到锁.
缺点就是:消耗了更多的CPU资源,其他线程可能吃不到CPU资源.

  • 挂起等待锁:就是在一个线程与其他线程产生锁冲突的时候,就会挂起等待,线程不再采用忙等的方式,而是阻塞等待,直到这个锁被释放,然后系统可以唤醒线程,再去尝试重新获取锁.

优点:不会占用CPU资源,可以把CPU资源让给其他的线程.
缺点:需要系统去唤醒该线程,再去获取锁,这样就不可以第一时间获取到锁.

举例说明:
有请老朋友:钟离,达达利亚,荧
如果达达利亚和荧去表白,但是现在荧告诉他,他现在有男朋友,但是达达利亚是个死皮赖脸的人,一直每天给荧发消息问候寒暄.一旦荧和现男友分手,达达利亚就立马可以上位.
如果钟离和荧去表白,但是现在荧也告诉他,他现在有男朋友,由于钟离比较收敛,不再去天天烦荧.但是有一天他从别人口中听说荧分手了,此时钟离才有机会上位.
在这里插入图片描述

1.4 公平锁vs非公平锁

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

  • 公平锁: 遵守先来后到的原则,B比C等待的时间长,在A释放锁之后,B就可以优先加锁.
  • 非公平锁: 不遵循先来后到的原则,B和C获取到锁的概率是相等的.就看谁的竞争能力比较强.

举例说明:
荧和他的男朋友在谈恋爱,突然有一天分手了,这时候达达利亚和钟离都有机会追到荧.但是现在达达利亚却比钟离追荧的时间长.
如果是公平锁的话:由于达达利亚追荧的时间比较长,那么达达利亚可以优先追到荧.
如果是非公平锁的话:这时就和追的时间没有关系了,这时候两个人追到荧的概率就是相等的,就各凭本事了.

注意:

  • 操作系统由于线程是随机调度的,这时候不做任何限制的话,那么锁就是非公平的.如果想要实现公平锁的话,必须借助额外的数据结构来判断等待时间.
  • 公平锁和非公平锁没有好坏之分,主要看使用场景.

1.5 不可重入锁vs可重入锁

前面提级过,不再赘述.
https://blog.csdn.net/2301_80050796/article/details/138041540?spm=1001.2014.3001.5501

1.6 读写锁

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

  • 两个线程都只是读一个数据,此时并没有线程安全问题,直接并发读取即可.读加锁和读加锁之间并不会产生锁互斥.
  • 如果两个线程有一个读,有一个写,此时有线程安全问题,写加锁和读加锁之间会有锁互斥.
  • 如果两个线程都在写一个数据,此时就有线程安全问题,写加锁和写加锁之间会产生锁互斥.

读写锁非常适合用在,频繁读,不频繁写的场景中.

2. synchronized原理

2.1 基本特点

  • 一开始是轻量级锁(乐观锁),如果锁冲突比较频繁,就升级为重量级锁(悲观锁)
  • 实现轻量级锁的时候大概率是使用自旋锁的策略.
  • 是一把不公平锁
  • 是可重入锁
  • 不是读写锁

2.2 synchronized的锁升级

JVM将synchronized锁分为无锁、偏向锁、轻量级锁、重量级锁状态.会根据情况,进⾏依次升级.
在这里插入图片描述

  1. 偏向锁(重点)
    第一个尝试加锁的线程,先不会对线程真正加锁.而是先进入偏向锁状态.
    偏向锁不是真正的加锁,而只是给对象头中做一个"偏向锁的标记",记录这个锁属于哪个线程,如果后期没有其他线程来与该线程进行锁竞争,那么就一直保持这个状态,就减少了加锁的开销.
    偏向锁就突出了一个字"懒",能不加锁就不加锁,避免了加锁带来的不必要的开销.但是这个偏向锁的标记又不得不做,否者分不清何时哪个线程需要真正加锁.
  2. 轻量级锁
    当有其他线程尝试与该线程进行锁竞争的时候,此时偏向锁就会升级为轻量级锁,
  3. 重量级锁
    当锁冲突不断加深的时候,就会升级为重量级锁.

举例说明:
此时比如荧和达达利亚是游走在朋友和男女朋友之间的关系,这时候如果没有其他人来与达达利亚竞争荧的时候,就一直可以保持这种关系(一直保持偏向锁),就避免了官宣这种开销比较大的操作,将来如果达达利亚想分手的时候,一句话直接让荧闭嘴:“你又不是我女朋友”.
如果有一天钟离也对荧有了感情,想要追荧,这时候达达利亚就可以立即和荧官宣,这种开销就比较大了(加锁操作),就意味着达达利亚要对荧负责了,将来如果想分手的时候,也非常麻烦,需要达达利亚一顿组合技才可以把荧踹掉.

2.3 synchronized锁优化

2.3.1 锁消除

编译器+JVM判断锁是否可消除.如果可以,就直接消除.
那么什么是锁消除呢?
有一些程序的代码中,虽然使用了synchronized关键字,但是是在单线程状态下的,比如下面这个例子.

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

上述的StringBuffer操作中,每一个append方法都会涉及加锁和解锁操作,但是这是在单线程状态下进行的,这时候就没有必要再进行加锁操作,系统就会把这层锁优化掉.

2.3.2 锁粗化

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

  • 锁的粒度
    主要看该加锁范围中包含代码的多少,包含的代码越多,就认为锁的粒度越粗,反之越细.
    在这里插入图片描述
    在我们实际开发中,使用细粒度的锁是为了避免在没有加锁的情况下,避免与其他的线程产生冲突,从而产生线程安全问题.
    但是在实际上可能并没有其他线程来抢占这个锁,这时候JVM就会自动把这几个锁粗化成更少的锁,从而减少加锁解锁的系统开销.

举例说明:
有请助教:滑稽老铁
滑稽⽼哥当了领导,给下属交代⼯作任务:⽅式⼀: • 打电话,交代任务1,挂电话. • 打电话,交代任务2,挂电话. • 打电话,交代任务3,挂电话.⽅式⼆: • 打电话,交代任务1,任务2,任务3,挂电话.显然,方式二是更高效的方案.

我们通过上面的解释可以看到,synchronized背后做的事情是非常多的,目的就是为了让Java程序员们即使不懂这些东西也可以写出高质量的代码.Java的大佬真的是为我们操碎了心!!
在这里插入图片描述

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

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

相关文章

奇偶校验码

目录 前言 校验原理简介 奇偶校验码 前言 在前两个文章的学习中,我们已经知道了数字字符这些简单的数据应该怎么在计算机内部进行表示,其实本质上是0101的二进制代码,但是这些数据在计算机内部进行计算存取和传送的过程中,由于计算机原器件可能会发生故障,也有可能因为某些…

python:set(集合)

set(集合) 去重处理,内容无序 列表使用:[] 元组使用:() 字符串使用:"" 集合使用:{} 基本语法; # 定义字面量集合:{元素,元素,元素,.......} 定义集合变…

【C语言】项目实践-贪吃蛇小游戏(Windows环境的控制台下)

一.游戏要实现基本的功能: • 贪吃蛇地图绘制 • 蛇吃食物的功能 (上、下、左、右方向键控制蛇的动作) • 蛇撞墙死亡 • 蛇撞自身死亡 • 计算得分 • 蛇身加速、减速 • 暂停游戏 二.技术要点 C语言函数、枚举、结构体、动态内存管…

用队列实现栈——leetcode刷题

题目的要求是用两个队列实现栈,首先我们要考虑队列的特点:先入先出,栈的特点:后入先出,所以我们的目标就是如何让先入栈的成员后出栈,后入栈的成员先出栈。 因为有两个队列,于是我们可以这样想&…

支付宝支付流程

第一步前端:点击去结算,前端将商品的信息传递给后端,后端返回一个商品的订单号给到前端,前端将商品的订单号进行存储。 对应的前端代码:然后再跳转到支付页面 // 第一步 点击去结算 然后生成一个订单号 // 将选中的商…

SQL 基础 | AVG 函数的用法

在SQL中,AVG()是一个聚合函数,用来计算某个列中所有值的平均值。 它通常与GROUP BY子句一起使用,以便对分组后的数据进行平均值计算。 AVG()函数在需要了解数据集中某个数值列的中心趋势时非常有用。 以下是AVG()函数的一些常见用法&#xff…

DETR类型检测网络实验2---优化测试

补全reference_point Anchor-DETR提出用预定义的参考点生成query_pos; DBA-DETR提出预定义参考信息由(x,y)增至(x,y,w,h) 那么在3D检测任务中是否可以把预定义参考信息补全为(x,y,z,l,w,h,sint,cost),而query_pos都是使用xy两个维度(因为是bev网络). (这种方法在Sparse-DETR中…

CMakeLists.txt语法规则:部分常用命令说明一

一. 简介 前一篇文章简单介绍了CMakeLists.txt 简单的语法。文章如下: CMakeLists.txt 简单的语法介绍-CSDN博客 接下来对 CMakeLists.txt语法规则进行具体的学习。本文具体学习 CMakeLists.txt语法规则中常用的命令。 二. CMakeLists.txt语法规则:…

探索LLM在广告领域的应用——大语言模型的新商业模式和新个性化广告的潜力

概述 在网络搜索引擎的领域中,广告不仅仅是一个补充元素,而是构成了数字体验的核心部分。随着互联网经济的蓬勃发展,广告市场的规模已经达到了数万亿美元,并且还在持续扩张。广告的经济价值不断上升,它已经成为支撑大…

C++初阶之模板初阶

一、泛型编程 如何实现一个通用的交换函数呢? void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {double temp left;left right;right temp; } void Swap(char& left,…

spring boot3多模块项目工程搭建-上(团队开发模板)

⛰️个人主页: 蒾酒 🔥系列专栏:《spring boot实战》 目录 写在前面 多模块结构优缺点 模块介绍 Common 模块: API 模块: Web 模块: Service 模块: DAO 模块: 搭建步骤 1.创建 父…

JavaWeb_请求响应_简单参数实体参数

一、SpringBoot方式接收携带简单参数的请求 简单参数:参数名与形参变量名相同,定义形参即可接收参数。并且在接收过程中,会进行自动的类型转换。 启动应用程序后,在postman中进行测试: 请求成功,响应回了O…

Flask教程3:jinja2模板引擎

文章目录 模板的导入与使用 模板的导入与使用 Flask通过render_template来实现模板的渲染,要使用这个方法,我们需要导入from flask import rander_template,模板中注释需放在{# #}中 模板的第一个参数为指定的模板文件名称,如自定…

微信小程序生成二维码加密(CryptoJS4.0加密PHP8.0解密)AES方式加密

1、小程序创建 crypto-js.js和crypto.js两个文件(点击文件即可) 2、小程序js页面引入 var crypto require(../../utils/crypto.js);//注意路径是否正确3、使用 let data {id: that.data.id,name: dx}console.log(JSON.stringify(data))console.log(&…

【论文阅读】Learning Texture Transformer Network for Image Super-Resolution

Learning Texture Transformer Network for Image Super-Resolution 论文地址Abstract1. 简介2.相关工作2.1单图像超分辨率2.2 Reference-based Image Super-Resolution 3. 方法3.1. Texture TransformerLearnable Texture Extractor 可学习的纹理提取器。Relevance Embedding.…

【八股】AQS,ReentrantLock实现原理

AQS 概念 AQS 的全称是 AbstractQueuedSynchronized (抽象队列同步器),在java.util.concurrent.locks包下面。 AQS是一个抽象类,主要用来构建锁和同步器,比如ReentrantLock, Semaphore, CountDownLatch,里…

Leetcode—163. 缺失的区间【简单】Plus

2024每日刷题&#xff08;126&#xff09; Leetcode—163. 缺失的区间 实现代码 class Solution { public:vector<vector<int>> findMissingRanges(vector<int>& nums, int lower, int upper) {int n nums.size();vector<vector<int>> an…

基于遗传优化模糊控制器的水箱水位控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 模糊控制器原理 4.2 遗传算法原理 4.3 遗传优化模糊控制器的工作流程 5.完整工程文件 1.课题概述 基于遗传优化模糊控制器的水箱水位控制系统simulink建模与仿真。对比模糊控制器和基于遗传优化的…

Python基础详解一

一&#xff0c;print打印 print("hello word") print(hello word) 双引号和单引号都可以 二&#xff0c;数据类型 Python中常用的有6种值的类型 输出类型信息 print(type(11)) print(type("22")) print(type(22.2)) <class int> <class str&…

飞书API(7):MySQL 入库通用版本

一、引入 在上一篇介绍了如何使用 pandas 处理飞书接口返回的数据&#xff0c;并将处理好的数据入库。最终的代码拓展性太差&#xff0c;本篇来探讨下如何使得上一篇的最终代码拓展性更好&#xff01;为什么上一篇的代码拓展性太差呢&#xff1f;我总结了几点&#xff1a; 列…