编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1String深入理解原理
  • 1.2浮点型数据深入研究
  • 1.3数据装箱和拆箱原理
  • 1.4泛型由来和设计思想
  • 1.5加密和解密设计和原理
  • 2.1面向对象设计思想
  • 2.2抽象类和接口设计
  • 2.3封装和继承设计思想
  • 2.4复用和组合设计思想
  • 2.5对象和引用设计思想
  • 3.1IO流设计思想和原理
  • 3.2为何设计序列化数据
  • 3.3各种拷贝数据比较
  • 3.4高效文件读写的原理
  • 4.1反射性能探索和优化
  • 4.2为何要设计注解思想
  • 4.3动态代理的设计思想
  • 4.4SPI机制设计的思想
  • 4.5异常设计和捕获原理
  • 4.6虚拟机如何处理异常
  • 4.7四种引用设计思想
  • 5.1线程的前世今生探索
  • 5.2线程通信的设计思想
  • 5.3线程监控和Debug设计
  • 5.4线程和JVM之间联系
  • 5.5线程池使用技巧介绍
  • 5.6线程池设计核心原理
  • 5.7线程如何最大优化
  • 6.1多线程并发经典案例
  • 6.2并发安全前世今生
  • 6.3线程安全如何保证
  • 6.4变量的线程安全探索
  • 6.5并发上下文切换原理
  • 6.6理解CAS设计和由来
  • 6.7协程设计思想和原理
  • 6.8事物并发模型解读
  • 6.9并发设计模型研究
  • 6.10并发编程数据一致性
  • 6.11锁问题的定位和修复
  • 6.12多线程如何性能调优
  • 7.1类的加载过程和原理
  • 7.2对象布局设计的原理
  • 7.3双亲委派机制设计思想
  • 7.5代码攻击和安全防护
  • 7.6设计动态生成Java类

6.11锁问题的定位和修复

目录介绍

  • 01.代码模拟产生死锁
    • 1.1 代码描述
    • 1.2 产生死锁代码
    • 1.3 死锁发生的场景
  • 02.Java中导致死锁的原因
    • 2.1 造成死锁原因
    • 2.2 死锁的危害
  • 03.出现死锁需要满足条件
    • 3.1 死锁问题条件
    • 3.2 如何预防死锁
  • 04.死锁诊断步骤
    • 4.1 如何定位死锁
  • 05.死锁修复解决方案
    • 5.1 死锁修复方案
  • 06.手写死锁代码
  • 07.死锁的三种类型案例
    • 7.1 死锁产生的条件
    • 7.2 静态的锁顺序死锁
    • 7.3 动态的锁顺序死锁
    • 7.4 协作对象之间发生的死锁

首先思考问题

  • 死锁的概念和产生死锁的根本原因是什么?死锁的预防策略中资源有序分配策略是什么。
  • 理解产生死锁的必要条件--以下四个条件同时具备:互斥条件、不可抢占条件、占有且申请条件、循环等待条件。这几个条件之间有什么关系。
  • 死锁的检测及恢复的思想。死锁是如何解决和避免的。结合具体案例分析一下思路?
  • Android实际开发中是否有遇到过死锁,在什么情景下遇到的,当时是怎么排查问题并且解决的?

01.代码模拟产生死锁

1.1 代码描述

  • 启动两个线程,设置两个线程监听对象obj1 、obj2。
    • 线程1启动的时候,先获取obj1锁,暂停1秒,然后获取obj2锁。
    • 线程2启动时,先获取obj2,再获取obj1
  • obj1和obj2如何产生死锁
    • 当线程2启动的时候获取obj2成功,然后去获取obj1的时候,obj1被线程1占用,此时就等待。
    • 线程1秒后去获取obj2,此时obj2锁被线程2握着,产生死锁,互相无法获取。

1.2 产生死锁代码

  • 代码如下所示
    • 这种方法将直接导致死锁
      private final Object obj1 = new Object();
      private final Object obj2 = new Object();
      private void test1() {
          new Thread(){
              @Override
              public void run() {
                  synchronized (obj1){
                      try {
                          System.out.println("yc---Thread1 obj1");
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      synchronized (obj2){
                          System.out.println("yc---Thread1 obj2");
                      }
                  }
              }
          }.start();
          new Thread(){
              @Override
              public void run() {
                  synchronized (obj2){
                      System.out.println("yc---Thread2 obj2");
                      synchronized (obj1){
                          System.out.println("yc---Thread2 obj1");
                      }
                  }
              }
          }.start();
      }
      
      //打印结果:
      10-18 17:11:34.255 30427-30478/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj1
      10-18 17:11:34.257 30427-30479/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj2
  • 接下来去掉上面代码中的Thread.sleep(1000)代码
    • 注意去掉了这行代码后,开始时可以正常打印日志,多次触发test1()方法后,发现最终也会出现死锁问题。
      private final Object obj1 = new Object();
      private final Object obj2 = new Object();
      private void test1() {
          new Thread(){
              @Override
              public void run() {
                  synchronized (obj1){
                      System.out.println("yc---Thread1 obj1");
                      synchronized (obj2){
                          System.out.println("yc---Thread1 obj2");
                      }
                  }
              }
          }.start();
          new Thread(){
              @Override
              public void run() {
                  synchronized (obj2){
                      System.out.println("yc---Thread2 obj2");
                      synchronized (obj1){
                          System.out.println("yc---Thread2 obj1");
                      }
                  }
              }
          }.start();
      }
      
      //打印日志
      10-18 17:35:44.662 4341-4412/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj1
      10-18 17:35:44.662 4341-4412/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj2
      10-18 17:35:44.663 4341-4413/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj2
      10-18 17:35:44.663 4341-4413/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj1
      10-18 17:35:45.967 4341-4417/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj1
      10-18 17:35:45.968 4341-4417/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj2
      10-18 17:35:45.968 4341-4418/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj2
      10-18 17:35:45.968 4341-4418/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj1
      10-18 17:35:46.643 4341-4422/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj1
      10-18 17:35:46.643 4341-4422/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj2
      10-18 17:35:46.647 4341-4423/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj2
      10-18 17:35:46.647 4341-4423/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj1
      10-18 17:35:46.913 4341-4424/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj1
      10-18 17:35:46.913 4341-4424/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj2
      10-18 17:35:46.913 4341-4425/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj2
      10-18 17:35:46.914 4341-4425/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj1
      10-18 17:35:47.117 4341-4426/com.yc.cn.ycbaseadapter I/System.out: yc---Thread1 obj1
      10-18 17:35:47.118 4341-4427/com.yc.cn.ycbaseadapter I/System.out: yc---Thread2 obj2

1.3 死锁发生的场景

  • 死锁不仅仅是在线程之间会发生,存在资源独占的进程之间同样也可能出现死锁。
    • 大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。

02.Java中导致死锁的原因

2.1 造成死锁原因

  • 在申请锁时发生了交叉闭环申请。即线程在获得了锁1并且没有释放的情况下去申请锁2,这时,另一个线程已经获得了锁2,在释放锁2之前又要先获得锁1,因此闭环发生,陷入死锁循环。
    • image
      image

2.2 死锁的危害

  • 从上面死锁代码案例可以知道,当发生死锁的时候,导致彼此一直处于等待之中,而导致代码无法执行下去。只能重启,后果比较严重!
  • 在死锁时,线程间相互等待资源,而又不释放自身的资源,导致无穷无尽的等待,其结果是系统任务永远无法执行完成。系统发生死锁现象不仅浪费大量的系统资源,甚至导致整个系统崩溃,带来灾难性后果。

3.出现死锁需要满足条件

3.1 死锁问题条件

    1. 互斥条件:一个资源每次只能被一个线程使用。
    1. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    1. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
    1. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

3.2 如何预防死锁

  • 死锁发生时的四个必要条件,只要破坏这四个必要条件中的任意一个条件,死锁就不会发生。这就为我们解决死锁问题提供了可能。一般地,解决死锁的方法分为死锁的预防,避免,检测[定位死锁的位置]与恢复三种(注意:死锁的检测与恢复是一个方法)。 锁的预防是保证系统不进入死锁状态的一种策略。它的基本思想是要求进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
    • 打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。
    • 打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。
    • 打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。但是,这种策略也有如下缺点:
      • 在许多情况下,一个进程在执行之前不可能知道它所需要的全部资源。这是由于进程在执行时是动态的,不可预测的;
      • 资源利用率低。无论所分资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被该进程用到一次,但该进程在生存期间却一直占有它们,造成长期占着不用的状况。这显然是一种极大的资源浪费;
      • 降低了进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数就必然少了。
    • 打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高,但是也存在以下缺点:
      • 限制了进程对资源的请求,同时给系统中所有资源合理编号也是件困难事,并增加了系统开销;
      • 为了遵循按编号申请的次序,暂不使用的资源也需要提前申请,从而增加了进程对资源的占用时间。

4.死锁诊断步骤

4.1 如何定位死锁

  • 定位死锁最常用的工具就是利用jstack等工具获取线程栈,然后定位相互之间的依赖关系,进而找到死锁。如果是比较明显的死锁,往往jstack工具就能直接定位,类似JConsole甚至可以在图形界面进行有限的死锁检测。
  • 如果程序运行时发生了死锁,绝大多数情况下都是无法在线解决的,只能重启、修正程序本身问题。所以,代码开发阶段相互审查,或者利用工具进行预防性排查,也是很重要的。
    • 1.首先,可以使用 jps 或者系统的 ps 命令、任务管理器等工具,确定进程 ID。
    • 2.调用 jstack 获取线程栈:jstack your-pid
    • 3.然后看看日志,思考怎么用studio将日志打印出来呢?

5.死锁修复解决方案

5.1 死锁修复方案

  • 如果在死锁检查时发现了死锁情况,那么就要努力消除死锁,使系统从死锁状态中恢复过来。消除死锁的几种方式:
    • 1.最简单、最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程;
    • 2.撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素;
    • 3.进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。

6.手写死锁代码

  • 死锁的简单代码
    • 思路是创建两个字符串a和b,再创建两个线程A和B,让每个线程都用synchronized锁住字符串(A先锁a,再去锁b;B先锁b,再锁a),如果A锁住a,B锁住b,A就没办法锁住b,B也没办法锁住a,这时就陷入了死锁。
    • 打印结果:可以看到,Lock1获取obj1,Lock2获取obj2,但是它们都没有办法再获取另外一个obj,因为它们都在等待对方先释放锁,这时就是死锁。
      public class DeadLock {
          public static String obj1 = "obj1";
          public static String obj2 = "obj2";
          public static void main(String[] args){
              Thread a = new Thread(new Lock1());
              Thread b = new Thread(new Lock2());
              a.start();
              b.start();
          }    
      }
      class Lock1 implements Runnable{
          @Override
          public void run(){
              try{
                  System.out.println("Lock1 running");
                  while(true){
                      synchronized(DeadLock.obj1){
                          System.out.println("Lock1 lock obj1");
                          Thread.sleep(3000);//获取obj1后先等一会儿,让Lock2有足够的时间锁住obj2
                          synchronized(DeadLock.obj2){
                              System.out.println("Lock1 lock obj2");
                          }
                      }
                  }
              }catch(Exception e){
                  e.printStackTrace();
              }
          }
      }
      class Lock2 implements Runnable{
          @Override
          public void run(){
              try{
                  System.out.println("Lock2 running");
                  while(true){
                      synchronized(DeadLock.obj2){
                          System.out.println("Lock2 lock obj2");
                          Thread.sleep(3000);
                          synchronized(DeadLock.obj1){
                              System.out.println("Lock2 lock obj1");
                          }
                      }
                  }
              }catch(Exception e){
                  e.printStackTrace();
              }
          }
      }
    • 如果我们只运行Lock1呢?修改一下main函数,把线程b注释掉。

07.死锁的三种类型案例

7.1 死锁产生的条件

  • 一般来说,要出现死锁问题需要满足以下条件:
    • 1.互斥条件:一个资源每次只能被一个线程使用。
    • 2.请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
    • 3.不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。
    • 4.循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
  • 在JAVA编程中,有3种典型的死锁类型:
    • 静态的锁顺序死锁,动态的锁顺序死锁,协作对象之间发生的死锁。

7.2 静态的锁顺序死锁

  • a和b两个方法都需要获得A锁和B锁。
    • 一个线程执行a方法且已经获得了A锁,在等待B锁;另一个线程执行了b方法且已经获得了B锁,在等待A锁。这种状态,就是发生了静态的锁顺序死锁。
      //可能发生静态锁顺序死锁的代码
      class StaticLockOrderDeadLock {
          private final Object lockA = new Object();
          private final Object lockB = new Object();
      
          public void a() {
              synchronized (lockA) {
                  synchronized (lockB) {
                      System.out.println("function a");
                  }
              }
          }
      
          public void b() {
              synchronized (lockB) {
                  synchronized (lockA) {
                      System.out.println("function b");
                  }
              }
          }
      }
  • 解决静态的锁顺序死锁的方法就是:所有需要多个锁的线程,都要以相同的顺序来获得锁。
    //正确的代码
    class StaticLockOrderDeadLock {
        private final Object lockA = new Object();
        private final Object lockB = new Object();
    
        public void a() {
            synchronized (lockA) {
                synchronized (lockB) {
                    System.out.println("function a");
                }
            }
        }
    
        public void b() {
            synchronized (lockA) {
                synchronized (lockB) {
                    System.out.println("function b");
                }
            }
        }
    }

7.3 动态的锁顺序死锁

  • 动态的锁顺序死锁是指两个线程调用同一个方法时,传入的参数颠倒造成的死锁。
    • 如下代码,一个线程调用了transferMoney方法并传入参数accountA,accountB;另一个线程调用了transferMoney方法并传入参数accountB,accountA。此时就可能发生在静态的锁顺序死锁中存在的问题,即:第一个线程获得了accountA锁并等待accountB锁,第二个线程获得了accountB锁并等待accountA锁。
      //可能发生动态锁顺序死锁的代码
      class DynamicLockOrderDeadLock {
          public void transefMoney(Account fromAccount, Account toAccount, Double amount) {
              synchronized (fromAccount) {
                  synchronized (toAccount) {
                      //...
                      fromAccount.minus(amount);
                      toAccount.add(amount);
                      //...
                  }
              }
          }
      }
  • 动态的锁顺序死锁解决方案如下:使用System.identifyHashCode来定义锁的顺序。确保所有的线程都以相同的顺序获得锁。
    //正确的代码
    class DynamicLockOrderDeadLock {
        private final Object myLock = new Object();
    
        public void transefMoney(final Account fromAccount, final Account toAccount, final Double amount) {
            class Helper {
                public void transfer() {
                    //...
                    fromAccount.minus(amount);
                    toAccount.add(amount);
                    //...
                }
            }
            int fromHash = System.identityHashCode(fromAccount);
            int toHash = System.identityHashCode(toAccount);
    
            if (fromHash < toHash) {
                synchronized (fromAccount) {
                    synchronized (toAccount) {
                        new Helper().transfer();
                    }
                }
            } else if (fromHash > toHash) {
                synchronized (toAccount) {
                    synchronized (fromAccount) {
                        new Helper().transfer();
                    }
                }
            } else {
                synchronized (myLock) {
                    synchronized (fromAccount) {
                        synchronized (toAccount) {
                            new Helper().transfer();
                        }
                    }
                }
            }
    
        }
    }

7.4 协作对象之间发生的死锁

  • 有时,死锁并不会那么明显,比如两个相互协作的类之间的死锁
    • 比如下面的代码:一个线程调用了Taxi对象的setLocation方法,另一个线程调用了Dispatcher对象的getImage方法。此时可能会发生,第一个线程持有Taxi对象锁并等待Dispatcher对象锁,另一个线程持有Dispatcher对象锁并等待Taxi对象锁。
      //可能发生死锁
      class Taxi {
          private Point location, destination;
          private final Dispatcher dispatcher;
      
          public Taxi(Dispatcher dispatcher) {
              this.dispatcher = dispatcher;
          }
      
          public synchronized Point getLocation() {
              return location;
          }
      
          public synchronized void setLocation(Point location) {
              this.location = location;
              if (location.equals(destination))
                  dispatcher.notifyAvailable(this);//外部调用方法,可能等待Dispatcher对象锁
          }
      }
      
      class Dispatcher {
          private final Set<Taxi> taxis;
          private final Set<Taxi> availableTaxis;
      
          public Dispatcher() {
              taxis = new HashSet<Taxi>();
              availableTaxis = new HashSet<Taxi>();
          }
      
          public synchronized void notifyAvailable(Taxi taxi) {
              availableTaxis.add(taxi);
          }
      
          public synchronized Image getImage() {
              Image image = new Image();
              for (Taxi t : taxis)
                  image.drawMarker(t.getLocation());//外部调用方法,可能等待Taxi对象锁
              return image;
          }
      }
    • 上面的代码中, 我们在持有锁的情况下调用了外部的方法,这是非常危险的(可能发生死锁)。为了避免这种危险的情况发生, 我们使用开放调用。如果调用某个外部方法时不需要持有锁,我们称之为开放调用。
  • 解决协作对象之间发生的死锁:需要使用开放调用,即避免在持有锁的情况下调用外部的方法。
    //正确的代码
    class Taxi {
        private Point location, destination;
        private final Dispatcher dispatcher;
    
        public Taxi(Dispatcher dispatcher) {
            this.dispatcher = dispatcher;
        }
    
        public synchronized Point getLocation() {
            return location;
        }
    
        public void setLocation(Point location) {
            boolean flag = false;
            synchronized (this) {
                this.location = location;
                flag = location.equals(destination);
            }
            if (flag)
                dispatcher.notifyAvailable(this);//使用开放调用
        }
    }
    
    class Dispatcher {
        private final Set<Taxi> taxis;
        private final Set<Taxi> availableTaxis;
    
        public Dispatcher() {
            taxis = new HashSet<Taxi>();
            availableTaxis = new HashSet<Taxi>();
        }
    
        public synchronized void notifyAvailable(Taxi taxi) {
            availableTaxis.add(taxi);
        }
    
        public Image getImage() {
            Set<Taxi> copy;
            synchronized (this) {
                copy = new HashSet<Taxi>(taxis);
            }
            Image image = new Image();
            for (Taxi t : copy)
                image.drawMarker(t.getLocation());//使用开放调用
            return image;
        }
    }

7.5 3种死锁的总结

  • 综上,是常见的3种死锁的类型。

  • 即:静态的锁顺序死锁,动态的锁顺序死锁,协作对象之间的死锁。在写代码时,要确保线程在获取多个锁时采用一致的顺序。同时,要避免在持有锁的情况下调用外部方法。

  • 什么情况下Java程序会产生死锁?如何定位、修复?

    • https://time.geekbang.org/column/article/9266
贡献者: yangchong211
上一篇
6.10并发编程数据一致性
下一篇
6.12多线程如何性能调优