当前位置: 首页 > news >正文

可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用

 一、💛

锁策略——接上一篇

6.分为可重入锁,不可重入锁

如果一个线程,针对一把锁,连续加锁两次,会出现死锁,就是不可重入锁,不会出现死锁,就是可重入锁。

如果一个线程,针对一把锁,连续加锁两次,如果产生了死锁,就是不可重入锁😄

public class Demo5 {public static void main(String[] args) {Thread t=new Thread(new Runnable() {int count=0;@Overridepublic synchronized void run() {    //加了一层锁synchronized (this){            //加了第二次锁count++;}System.out.println(count);}});t.start();}
}

那么我们来解释一下什么叫做死锁呢?

public synchronized void run() {    //加了一层锁
                synchronized (this){            //加了第二次锁
                    count++;
                }

这个代码中,调用方法先针对this加锁,此时假设加锁成功了,接下来到往下执行代码块中的this来进行加锁,此时就会出现锁竞争,this已经处于锁状态了,此时该线程就会阻塞~一直阻塞到锁被释放,才能有机会拿到锁。

这也是死锁第一个体现:this这个锁必须要run执行完毕,才能释放,但是要想执行完事,这个第二次加锁就应该加上,方法才可以执行,但是第二次想加上第一个就应该放锁,所以由于this锁没法释放,代码就卡在这里了,因此线程数量就僵住了。

还好synchronized是可重入锁,JVM帮我们承担了很多的任务

这里卡死就很不科学的一种情况,第二次尝试加锁的时候,该线程已经有了这个锁的权限了~~这个时候,不应该加锁失败,不应该进行阻塞等待的~

不可重入锁:这把锁不会保存,哪个线程加上的锁,只要他当前处于加锁状态之后,收到了‘加锁的请求’,就会拒绝当前加锁,而不管当下线程是哪个,就会产生死锁。 🌝

可重入锁:会让这个锁保存,是哪个线程加的锁,后续收到加锁请求之后,就会先对比一下,看看加锁的线程是不是当前持有自己这把锁的线程~~这个时候就可以灵活判定了。

那么该如何对比捏🌚

synchronized(this){synchronized(this){synchronized(this){
······->执行到这个代码,出了这个代码,刚才加上的锁,是否要释放?}       如果最里面的释放了锁,意味着最外面的synchronized和中间的synchronized后
}           续的代码部分就没有处在锁的保护之中了

真正要在这个地方释放锁,如加锁N层遇到了 } , JVM如何知道是最后一个呢,整一个整型变量,记录当前这个线程加了几次锁,每遇到一个加锁操作,计数器+1,每遇到一个解锁操作,就-1,当计数器减为0时,才真正执行释放锁操作,其他时候时不释放的。这一个思想就叫做‘引用计数’🐲🐲🐲(脑力+10000,人类进化不带我)

注补充:静态方法是针对类加锁,普通方法是针对this加锁


二、💙

死锁的详细介绍:两次加锁,都是同一个线程

死锁的三种典型情况;

1.一个线程,一把锁,但是不可入锁,该线程针对这个锁联系加两次就会出现死锁。

2.两个锁,两个锁,这两个县层先分别获取一把锁,然后再尝试分别获取对方的锁(

比如我拿了酱油要炫饺子,小杨拿醋,我让他把醋先给我,然后我一起给你,小杨一拍桌子,凭啥先给你,你多个啥?),如下图,双方陷入死循环中

 

public class Demo5 {public static Object  locker1=new Object();public static Object  locker2=new Object();public static void main(String[] args) {Thread t1=new Thread(new Runnable() {@Overridepublic synchronized void run() {synchronized (locker1) {           //给1加锁System.out.println("s1.start");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2) {    //没有放弃1的锁System.out.println("s2.over");}}}});t1.start();Thread t2=new Thread(new Runnable() {@Overridepublic synchronized void run() {synchronized (locker2) {         //給2加锁System.out.println("t2.start");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker1) {     //没有放弃2的锁System.out.println("t1.over");}}}});t2.start();}
}

3.N个线程,M把锁:

(哲学家就餐问题)


 三、💜

如何应该避免死锁呢?先明确死锁产生的原因,死锁的必要条件(缺一不可)

1.互斥使用:一个线程获取到一把锁之后,别的线程不能获取到这个锁。(实际使用的锁,一般都是互斥的锁的基本特性)

2.不可抢占:锁只能被持有者主动释放,而不能是其他线程直接抢走(也是锁的基本特性)

3.请求和保持:这一个线程尝试获取多把锁,在获取第二把时候,会保持对第一把的获取状态(取决于代码结构)比如刚才写的,我只要让他获取完第一把再释放,在获取第二把,这样不发生冲突,但是可能会影响需求。

4.循环等待:t1尝试获取locker2,需要t2执行完释放locker2,t2尝试获取locker1,需要t1执行完毕,释放locker1(取决于代码结构)

我们的解决方式趋向于解决第四种(打破循环等待,如何具体实现解决死锁,实际方法有很多)

首先就是银行家算法(杀牛刀了属于,复杂,没必要会)

简单有效方法:针对锁进行编号,且规定加锁的顺序,只要线程加锁的顺讯,都严格执行上述顺序,就没有循环等待。

如下:

一般面试我们主动点:  问到死锁捡着了,细细的答,给他讲,让他觉得你是理解的

1.什么是死锁。           

2.死锁的几个典型场景

3.死锁产生的必要条件

4.如何解决死锁的问题

 


四、 ❤️

synchronized具体采用了哪些锁策略呢?

1.既是悲观锁,又是乐观锁

2.既是重量级锁,又是轻量级锁

3.重量级锁部分是基于多系统互斥锁实现的,轻量级锁部分是基于自旋锁实现的

4.synchronized是非公平锁(不会遵守先来后到,锁释放之后,哪个线程拿到锁个凭本事

5.synchronized是可重入锁(内部会记录哪个线程拿到了锁,记录引用计数)

6.synchronized不是读写锁

synchronized-内部实现策略(自适应)

讲解一下自适应:代码中写了一个synchhronized之后,可能产生一系列自适应的过程,锁升级(锁膨胀)

无锁->偏向锁->轻量级锁->重量级锁

偏向锁,不是真的加锁,而只是做了一个标记,如果有别的线程来竞争锁,才会真的加锁,如果没有别的线程竞争,就自始至终都不加锁了(渣女心态,没人来追你,我就钓鱼,你要是被追了,我先给你个身份,让别人别靠近你。)——当然加锁本身也有一定消耗

偏向锁在没人竞争的时候就是一个简单的(轻量的)标记,如果有别的线程来尝试加锁,就立即把偏向锁升级成真正加锁,让别人阻塞等待(能不加锁就不加锁)

轻量级锁-synchronized通过自旋锁的方式实现轻量级锁——这边把锁占据了,另一个线程按照自旋的方式(这个锁操作比较耗cpu,如果能够快速拿到锁,多耗点也不亏),来反复查询当前的锁状态是不是被释放,但是后续,如果竞争这把锁的线程越来越多了(锁冲突更加激烈了),从轻量锁,升级到重量级锁~随着竞争激烈,即使前一个线程释放锁,也不一定能够拿到锁,何时能拿到,时间可能比较久了会

 💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖

锁清除:编译器,会智能的判断,当前这个代码,是否有必要加锁,如果你写了加锁,但实际没必要加锁,就会自动清除锁

如:单个线程使用StringBuffer编译器进行优化,是保证优化之后的逻辑和之前的逻辑是一致的,这样就会让代码优化变的保守起来~~咱们猿们也不能指望编译器优化,来提升代码效率,自己也要有作用,判断何时加锁,也是咱们非常重要的工作。

锁粗化:

关于锁的粒度,锁中操作包含代码多:锁粒就大

//1号       全写的是伪代码
和2号比较明显是2号的粒度更大
for(
synchronized(this){count++}
}//2号
synchronized(this){
for{
count++}
}

锁粒大,锁粒小各有好处:

锁粒小,并发程度会更高,效率也会更快

锁粒大,是因为加锁本身就有开销。(如同打电话,打一次就行,老打电话也不好)

上述的都是基本面试题


五、💚

CAS全称(Compare and swap) 字面意思:比较并且交换

能够比较和交换,某个寄存器中的值和内存中的值,看是否相等,如果相等就把另一个寄存器中的值和内存进行交换

boolean CAS(address,expectValue,swapValue){if(&address==expectValue){         //这个&相当于C语言中的*,看他两个是否相等&address=swapValue;             //相等就换值return true;                 
}return false;

此处严格的说是,adress内存的值和swapValue寄存器里的值,进行交换,但是一般我们重点关注的是内存中的值,寄存器往往作为保存临时数据的方式,这里的值是啥,很多时候我们选择是忽略的。

这一段逻辑是通过一条cpu指令完成的(原子的,或者说确保原子性)给我们编写线程安全代码,打开了新的世界。

CAS的使用

1.实现原子类:多线程针对一个count++,在java库中,已经提供了一组原子类

java.util.concurrent(并发的意思).atomic

AtomicInteger,AtomicLong,提供了自增/自减/自增任意值,自减任意值··,这些操作可以基于CAS按照无锁编程的方式来实现。

如:for(int i=0;i<5000;i++){

count.getAndIncrement();                         //count++

count.incrementAndGet();                        //++count

count.getAndDecrement();                      //count--

count.decrementAndGet()                      //--count

}

import java.util.concurrent.atomic.AtomicInteger;public class Demo6 {public  static AtomicInteger count=new AtomicInteger(0);    //这个类的初值呗public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i=0;i<500;i++){count.getAndIncrement();}});Thread t2=new Thread(()->{for (int i=0;i<500;i++){count.getAndIncrement();}});t1.start();t2.start();t1.join();            //注意要等待两个线程都结束再开始调用t2.join();System.out.println(count);}
}

上述原子类就是基于CAS完成的

当两个线程并发的线程执行++的时候,如果加限制,意味着这两个++是串行的,能计算正确的,有时候者两个++操作是穿插的,这个时候是会出现问题的

加锁保证线程安全:通过锁,强制避免出现穿插~~

原子类/CAS保证线程安全,借助CAS来识别当前是否出现穿插的情况,如果没有穿插,此时直接修改就是安全的,如果出现了穿插,就会重新读取内存中最新的值,再次尝试修改。

部分源码合起来的意思就是 

public int getAndIncrement(){int oldValue=value;       //先储存值,防止别的线程偷摸修改之后,无法恢复到之前的值while(CAS(Value,oldValue,OldValue+1)!=true){  //检查是否线程被别的偷摸修改了//上面的代码是Value是否等于oldValue,假如等于就把Value赋值OldValue+1oldValue=value;                        //假如修改了就恢复了原来的样子}return oldValue;}

 

假如这种情况,刚开始设置value=0, 

CAS是一个指令,这个指令本身是不能够拆分的。

是否可能会出现,两个线程,同时在两个cpu上?微观上并行的方式来执行,CAS本身是一个单个的指令,这里其实包含了访问操作,当多个cpu尝试访问内存的时候,本质也是会存在先后顺序的。

就算同时执行到CAS指令,也一定有一个线程的CAS先访问到内存,另一个后访问到内存

为啥CAS访问内存会有先后呢?

多个CPU在操作同一个资源,也会涉及到锁竞争(指令级别的锁),是比我们平时说的synchronized代码级别的锁要轻量很多(cpu内部实现的机制) 

相关文章:

可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用

一、&#x1f49b; 锁策略——接上一篇 6.分为可重入锁&#xff0c;不可重入锁 如果一个线程&#xff0c;针对一把锁&#xff0c;连续加锁两次&#xff0c;会出现死锁&#xff0c;就是不可重入锁&#xff0c;不会出现死锁&#xff0c;就是可重入锁。 如果一个线程&#xff0c;针…...

JSON.parse()和JSON.stringify()用法

JSON.parse() 方法用于将 JSON 格式的字符串转换为 JavaScript 对象&#xff0c;而 JSON.stringify() 方法用于将 JavaScript 对象转换为 JSON 字符串。这两个方法可以组合使用来实现将数据从对象到字符串再到对象的转换。 示例 // 创建一个包含属性的 JavaScript 对象 var pe…...

Android 并发编程--阻塞队列和线程池

一、阻塞队列 队列是一种特殊的线性表&#xff0c;特殊之处在于它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&#xff0c;和栈一样&#xff0c;队列是一种操作受限制的线性表。进行插入操作…...

Playwright快速上手-1

前言 随着近年来对UI自动化测试的要求越来越高&#xff0c;,功能强大的测试框架也不断的涌现。本系列主讲的Playwright作为一款新兴的端到端测试框架,凭借其独特优势,正在逐渐成为测试工程师的热门选择。 本系列文章将着重通过示例讲解 Playwright python开发环境的搭建 …...

PPT颜色又丑又乱怎么办?

一、设计一套PPT时&#xff0c;可以从这5个方面进行设计 二、PPT颜色 &#xff08;一&#xff09;、PPT常用颜色分类 一个ppt需要主色、辅助色、字体色、背景色即可。 &#xff08;二&#xff09;、搭建PPT色彩系统 设计ppt时&#xff0c;根据如下几个步骤&#xff0c;依次选…...

python计算相关系数R

方法一&#xff1a; import numpy as np# 计算相关系数R def r(y_true, y_pred):y_true np.array(y_true)y_pred np.array(y_pred)corr np.corrcoef(y_true, y_pred)[0][1]return corrcorr r(yture, ypred)方法二 import scipy.stats # 计算皮尔逊相关指数&#xff0c;并…...

黑马项目一阶段面试 自我介绍篇

面试官你好&#xff0c;我叫xxx&#xff0c;是来自xxxx的本科毕业生。我通过招聘网站/内推/线下招聘了解到的贵司&#xff0c;我具有扎实的Java后端的基础功底&#xff0c;基本掌握JavaSE、JavaEE流行技术的使用&#xff0c;并且我比较好学&#xff0c;心态也很乐观积极&#x…...

时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测

时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测 目录 时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现CNN-BiGRU-Attention时间序列预测&#xff0c;CNN-BiGRU-Attention结合注意力机制时…...

开发过程中遇到的问题以及解决方法

巩固基础&#xff0c;砥砺前行 。 只有不断重复&#xff0c;才能做到超越自己。 能坚持把简单的事情做到极致&#xff0c;也是不容易的。 开发过程中遇到的问题以及解决方法 简单易用的git命令 git命令&#xff1a; 查看有几个分支&#xff1a;git branch -a 切换分支&#…...

本地oracle登录账号锁定处理,the account is locked

1.打开cmd命令窗口 2.打开sqlplus: sqlplus /nolog(加/nolog是不登录服务器的意思&#xff0c;不加就需要输账号密码) 3.切换到管理员&#xff1a;conn / as sysdba; 第2步第3步可以合并&#xff0c;直接使用sysdba登录&#xff1a;sqlplus / as sysdba; 4.解锁账号&#x…...

redission自定义hessian序列化

一。技术改造背景 由于之前的比较陈旧的技术&#xff0c;后面发起了技术改造&#xff0c;redis整体改后使用redisson框架。 二。问题 改造完成后&#xff0c;使用方反馈 缓存获取异常 异常信息如下 Caused by: java.io.CharConversionException: Unexpected EOF in the mid…...

P8642 [蓝桥杯 2016 国 AC] 路径之谜

[蓝桥杯 2016 国 AC] 路径之谜 题目描述 小明冒充 X X X 星球的骑士&#xff0c;进入了一个奇怪的城堡。 城堡里边什么都没有&#xff0c;只有方形石头铺成的地面。 假设城堡地面是 n n n\times n nn 个方格。如图所示。 按习俗&#xff0c;骑士要从西北角走到东南角。 …...

oracle sql developer批量删除某个用户

随着navicate收费&#xff0c;还得破解&#xff0c;pl/sql developer配置麻烦&#xff0c;最近使用oracle sql developer来试试oracle的操作如何&#xff1b; 用着还行&#xff0c;没有卡顿现象&#xff0c; 最近要oracle sql developer批量删除某个用户下所有的表&#xff0…...

k8s 滚动更新控制(一)

在传统的应用升级时&#xff0c;通常采用的方式是先停止服务&#xff0c;然后升级部署&#xff0c;最后将新应用启动。这个过程面临一个问题&#xff0c;就是在某段时间内&#xff0c;服务是不可用的&#xff0c;对于用户来说是非常不友好的。而kubernetes滚动更新&#xff0c;…...

Java智慧工地APP源码带AI识别

智慧工地为建筑全生命周期赋能&#xff0c;用创新的可视化与智能化方法&#xff0c;降低成本&#xff0c;创造价值。 一、智慧工地APP概述 智慧工地”立足于互联网&#xff0c;采用云计算&#xff0c;大数据和物联网等技术手段&#xff0c;针对当前建筑行业的特点&#xff0c;…...

ME3116电源小板

最近设计一款PCB的时候使用微盟的dc dc电源ic踩了一个坑。 在使用me3116作为24v到5v的降压ic作为esp32系统前级的降压电路时&#xff0c;再没有铂电阻采样负载的情景下工作正常&#xff0c;带上负载后&#xff0c;ic工作不正常&#xff0c;过一段时间&#xff0c;后级电路会烧…...

摸准天气“小心思”,躲避恶劣天气“偷袭”

打开天气预报一看&#xff0c;天气真的很“善变”&#xff0c;既是高温又暴雨&#xff0c;偶尔还有台风路过&#xff0c;“蒸”的让人太太太难受了。看着天气在放晴和即将下雨之间“徘徊”&#xff0c;总是纠结带不带雨伞&#xff0c;让我的每次出门都成了一场冒险之旅。 持…...

Golang 局部变量、全局变量 声明

文章目录 一、局部变量二、全局变量 一、局部变量 四种声明方式 多变量声明&#xff1a; package mainimport "fmt"//局部变量声明 func main() {//方法一: 声明一个变量和数据类型&#xff0c;不初始化值&#xff1b;默认值为0&#xff1b;var lvA intfmt.Printl…...

软考高级之系统架构师之数据通信与计算机网络

概念 OSPF 在划分区域之后&#xff0c;OSPF网络中的非主干区域中的路由器对于到外部网络的路由&#xff0c;一定要通过ABR(区域边界路由器)来转发&#xff0c;既然如此&#xff0c;对于区域内的路由器来说&#xff0c;就没有必要知道通往外部网络的详细路由&#xff0c;只要由…...

牛客网华为OD前端岗位,面试题库练习记录01

题目一 质数因子 功能:输入一个正整数&#xff0c;按照从小到大的顺序输出它的所有质因子&#xff08;重复的也要列举&#xff09;&#xff08;如180的质因子为2 2 3 3 5 &#xff09; JavaScript Node ACM模式 const rl require("readline").createInterface({ i…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...

LangFlow技术架构分析

&#x1f527; LangFlow 的可视化技术栈 前端节点编辑器 底层框架&#xff1a;基于 &#xff08;一个现代化的 React 节点绘图库&#xff09; 功能&#xff1a; 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...