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

【JavaEE】了解synchronized

synchronized也称 加锁,当一个操作为非原子性操作时,可以通过 加锁 成为原子性操作。synchronized可以修饰代码块、实例方法以及静态方法。synchronized的重要特性:可重入锁。

关于死锁

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

2.两个线程针对两把锁,无论是否是可重入锁,都会出现死锁现象。

3.n个线程,m把锁,更容易出现死锁现象(经典问题:哲学家就餐问题)

1.一个线程一把锁

可重入性:一个线程针对一把锁,加锁两次不会出现死锁现象。

死锁:“车里锁了家门钥匙,家里锁了车钥匙”这个可以称为死锁,使得该锁无法解开的锁就称为死锁。如下例子:在还不知道synchronized有可重入性这个概念时,都会默认以下代码是死锁现象

public class Demo01 {public static void main(String[] args) {Object lock = new Object();Thread t1 = new Thread(()->{synchronized (lock){synchronized(lock){System.out.println("nihao");}}});t1.start();System.out.println("hahaha");}
}

synchronized 第一次加锁已经成功了,lock就属于被锁定的状态,第二次再加锁,就应该处于阻塞状态,只有等上一次lock锁被释放后才能加锁成功,但是在进行第二次加锁时就已经阻塞了,程序无法往后继续进行就会出现死锁的现象。这种情况只是在我们不知道synchronized的特性的情况下的想法,正是它有这个可重入性,就不会出现以上的死锁现象。

    接着我们进一步分析synchronized是如何做到不会出现死锁的:

当synchronized第一次加锁成功后,又遇到了第二次加锁,此时的第一次加锁并没有释放,可重入性是通过计数器实现的,所以锁对象中不仅需要记录被谁使用还需要记使用次数。正如上面这个例子:当一次加锁成功后,lock这个锁对象中的计数器随其加一,第二次在加锁时计数器也所着加一,直到遇到一个右大括号,相当于解一次锁计数器就减一,这样就是可重入锁。为此锁对象真正释放锁必须是计数器为零时。

2.两个线程两把锁

已知 线程t1,线程t2,锁A,锁B,线程t1已经获得A、线程t2已经获得B,t1还想获得B,t2还想获得A,此时就会出现死锁现象。如下:

public static void main(String[] args) {Object A = new Object();Object B = new Object();Thread t1 = new Thread(()->{synchronized (A){System.out.println("t1->A");synchronized (B){System.out.println("t1->B");}}});Thread t2 = new Thread(()->{synchronized (B){System.out.println("t2->B");synchronized (A){System.out.println("t2->A");}}});t1.start();t2.start();}

以上代码不太严谨,还需要加强,可能会出现一个线程同时获得AB两把锁的情况,所以需要加一个sheep休眠。如下结果,两个线程就出现阻塞现象,两个线程僵持不下就阻塞进程。相反如果是线程t1获得A后又释放,再获得B,这是不会出现死锁现象的。

3.N个线程M把锁

哲学家就餐问题

在圆形餐桌上,每两个哲学家中间就一只筷子,每个哲学家只会作两件事,一件是思考(不拿筷子),另一件是吃面(只能拿左右两边的筷子),每个哲学家思考和吃面都是随机的,当左右边筷子少一个都得等两边哲学家吃完才能拿筷子(等价于阻塞等待),一般情况下是可以正常运行的,如果是极端情况下,就会出现已知等待线程,每个哲学家都拿左手边的筷子,并且都在等右手边的哲学家放下筷子,这时每个人都在等,就等同于出现死锁现象了。

死锁是一个很严重的bug,它会直接导致线程卡住,无法执行后面工作。那我们程序员就需要解决bug,接着就学习如何解决死锁问题。

解决死锁问题

死锁的成因

想要解决死锁问题,需要先了解死锁的成因

1.互斥使用(锁的基本特性),当一个线程持有一把锁之后,另一个线程也想获得该锁,这个线程就会出现阻塞等待。

2.不可抢占(锁的基本特性),当锁已经被线程t1拿到之后,线程t2只能等待线程t1主动释放锁,而不能强行占有锁。

3.请求保持(代码结构),一个线程尝试获得多把锁(如:上面两个线程两把锁这个例子)。

4.循环等待/环路等待(代码结构),等待关系形成环(如:上面N个线程M把锁、家钥匙锁车里,家里锁车钥匙)。

出现死锁就需要满足以上四个条件,缺一不可,而第一第二都是锁的特性本身就满足条件,只要满足三和四就会出现死锁现象。

解决死锁

既然要解决死锁,那就让以上条件其中有一是不满足即可:

面对1和2,既是锁的特性,那就是不可修改的,我们只需要破坏3和4其中一个即可。

针对3:我们可以通过调整代码避免出现“锁嵌套”,比如:将嵌套关系改为并列关系;

针对4:如果一定需要嵌套关系,那可以约定加锁顺序,针对锁进行编号,比如:加多把锁时先加编号小的后加编号大的,针对所有线程都遵循这个约定。

http://www.xdnf.cn/news/18600.html

相关文章:

  • Java 基础学习总结(211)—— Apache Commons ValidationUtils:让参数校验从 “体力活“ 变 “优雅事“
  • 电动车运行原理与最新人工智能驾驶技术在电动车上的应用展望:从基础动力系统到L5级完全自动驾驶的技术深度解析
  • 大语言模型的自动驾驶 LMDrive/DriveVLM-Dual
  • Kubernetes部署Prometheus+Grafana 监控系统NFS存储方案
  • Spark04-MLib library01-机器学习的介绍
  • Spring创建的方式
  • 在 Ubuntu 24.04 或 22.04 LTS 服务器上安装、配置和使用 Fail2ban
  • 【LLM】DeepSeek-V3.1-Think模型相关细节
  • Android - 用Scrcpy 将手机投屏到Windows电脑上
  • MySQL学习记录-基础知识及SQL语句
  • SSRF的学习笔记
  • React useState 全面深入解析
  • 6.2 el-menu
  • Axure RP 9的安装
  • 如何让FastAPI在百万级任务处理中依然游刃有余?
  • Postman参数类型、功能、用途及 后端接口接收详解【接口调试工具】
  • 【C++】函数返回方式详解:传值、传引用与传地址
  • Linux 824 shell:expect
  • 今日科技热点 | AI加速创新,5G与量子计算引领未来
  • PHP - 实例属性访问与静态方法调用的性能差异解析
  • B站视频字幕提取工具
  • mysql 5.7 查询运行时间较长的sql
  • 【计算机408数据结构】第三章:基本数据结构之栈
  • 苍穹外卖项目实战(日记十)-记录实战教程及问题的解决方法-(day3-2)新增菜品功能完整版
  • 启动Flink SQL Client并连接到YARN集群会话
  • 拓展:simulink中将仿真环境离散化
  • K8S的部署与常用管理
  • VS2022的MFC中关联使用控制台并用printf输出调试信息
  • Redis 高可用篇
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十四)垂直滚动条