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

【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.dtcms.com/a/347962.html

相关文章:

  • 大数据毕业设计选题推荐-基于大数据的丙型肝炎患者数据可视化分析系统-Hadoop-Spark-数据可视化-BigData
  • 【数据结构】从基础到实战:全面解析归并排序与计数排序
  • 基于stm32汽车雨刮器控制系统设计
  • Java基础第3天总结(面向对象)
  • Shell Case 条件语句详解
  • EP01:【DA】数据分析的概述
  • 01Shell脚本入门:基础命令与变量解析
  • JVM之【类加载系统】
  • 【Qt开发】常用控件(六)
  • Golang云端编程深度指南:架构本质与高阶实践
  • Flink Slot 不足导致任务Pending修复方案
  • 互联网大厂Java面试实录:从Spring到微服务的全面考察
  • 【软件安全】ARM64、x86、32 位与 64 位架构的区别、定义、应用背景
  • 个人搭建小网站教程(云服务器Ubuntu版本)
  • 【数据结构】二叉树的顺序存储、堆的实现及其应用:堆排序与Top-K问题
  • 以国产IoTDB为代表的主流时序数据库架构与性能深度选型评测
  • kanass V1.1.4版本发布,支持Mysql数据库、ubuntu安装与Mantis数据导入
  • Thonny+MicroPython搭建ESP32芯片开发环境
  • 代码性能测试——benchmark库
  • Elasticsearch Ruby 客户端故障排查实战指南
  • AI与SEO关键词协同优化
  • DBeaver连接SQL Server集成认证问题解决方案
  • xxl-job 启动后导致pod内存使用率持续增加
  • 从 Unity UGUI 到 Unreal UMG 的交互与高效实践:UI 事件、坐标系适配与性能优化
  • MATLAB 与 Simulink 联合仿真:控制系统建模与动态性能优化
  • C#_gRPC
  • RabbitMQ--消费端异常处理与 Spring Retry
  • 阿里云拉取dockers镜像
  • 在JavaScript中,比较两个数组是否有相同元素(交集)的常用方法
  • 今日科技热点 | AI加速创新,5G与量子计算引领未来