zoukankan      html  css  js  c++  java
  • Java多线程 开发中避免死锁的八种方法

    1. 设置超时时间

    使用JUC包中的Lock接口提供的tryLock方法. 
    该方法在获取锁的时候, 可以设置超时时间, 如果超过了这个时间还没拿到这把锁, 那么就可以做其他的事情, 而不是像 synchronized 如果没有拿到锁会一直等待下去.

                          boolean   tryLock   (   long  time  ,  TimeUnit unit  )   throws  InterruptedException  ;   

    造成超时的原因有很多种:发生了死锁, 线程进入了死循环, 线程逻辑复杂执行慢.

    到了超时时间, 那么就获取锁失败, 就可以做一些记录操作, 例如 打印错误日志, 发送报警邮件,提示运维人员重启服务等等.

    如下的代码演示了 使用tryLock 来避免死锁的案例. 
    线程1 如果拿到了锁1 , 那么就在指定的800毫秒内去尝试拿到锁2, 如果两把锁都拿到了 , 那么就释放这两把锁. 如果在指定的时间内, 没有拿到锁2 , 那么就释放锁1 .

    线程2 与线程1相反, 先去尝试拿到锁2, 如果拿到了, 就去在3s内尝试拿到锁1, 如果拿到了, 那么就释放锁1和2, 如果3s内没有拿到锁1, 那么释放锁2 .

                          package  com  .  thread  .  deadlock  ;   import  java  .  util  .  Random  ;   import  java  .  util  .  concurrent  .  TimeUnit  ;   import  java  .  util  .  concurrent  .  locks  .  Lock  ;   import  java  .  util  .  concurrent  .  locks  .  ReentrantLock  ;   /**
     * 类名称:TryLockDeadlock
     * 类描述:  使用lock接口提供的trylock 避免死锁
     *
     * @author: https://javaweixin6.blog.csdn.net/
     * 创建时间:2020/9/12 17:23
     * Version 1.0
     */   public   class   TryLockDeadlock   implements   Runnable   {   int  flag  =   1   ;   //ReentrantLock 为可重入锁   static  Lock lock1  =   new   ReentrantLock   (   )   ;   static  Lock lock2  =   new   ReentrantLock   (   )   ;   public   static   void   main   (  String  [   ]  args  )   {   // 创建两个线程 给出不同的flag  并启动  TryLockDeadlock r1  =   new   TryLockDeadlock   (   )   ;  TryLockDeadlock r2  =   new   TryLockDeadlock   (   )   ;  r1  .  flag  =   1   ;  r2  .  flag  =   0   ;   new   Thread   (  r1  )   .   start   (   )   ;   new   Thread   (  r2  )   .   start   (   )   ;   }   @Override   public   void   run   (   )   {   for   (   int  i  =   0   ;  i  <   100   ;  i  ++   )   {   if   (  flag  ==   1   )   {   //先获取锁1  再获取锁2   try   {   //给锁1 800毫秒与获取锁, 如果拿到锁, 返回true, 反之返回false   if   (  lock1  .   tryLock   (   800   ,  TimeUnit  .  MICROSECONDS  )   )   {  System  .  out  .   println   (   "线程1获取到了锁1  "   )   ;   //随机的休眠  Thread  .   sleep   (   new   Random   (   )   .   nextInt   (   1000   )   )   ;   if   (  lock2  .   tryLock   (   800   ,  TimeUnit  .  MICROSECONDS  )   )   {  System  .  out  .   println   (   "线程1获取到了锁2  "   )   ;  System  .  out  .   println   (   " 线程1 成功获取了两把锁   "   )   ;   //释放两把锁, 退出循环  lock2  .   unlock   (   )   ;  lock1  .   unlock   (   )   ;   break   ;   }   else   {  System  .  out  .   println   (   " 线程1尝试获取锁2 失败, 已经重试  "   )   ;   //释放锁1  lock1  .   unlock   (   )   ;   //随机的休眠  Thread  .   sleep   (   new   Random   (   )   .   nextInt   (   1000   )   )   ;   }   }   else   {  System  .  out  .   println   (   " 线程1 获取锁1失败, 已重试  "   )   ;   }   }   catch   (   InterruptedException  e  )   {  e  .   printStackTrace   (   )   ;   }   }   if   (  flag  ==   0   )   {   //先获取锁2  再获取锁1. 并且尝试获取锁的时间变长 ,改成3s   try   {   //给锁1 800毫秒与获取锁, 如果拿到锁, 返回true, 反之返回false   if   (  lock2  .   tryLock   (   3000   ,  TimeUnit  .  MICROSECONDS  )   )   {  System  .  out  .   println   (   "线程2获取到了锁2  "   )   ;   //随机的休眠  Thread  .   sleep   (   new   Random   (   )   .   nextInt   (   1000   )   )   ;   if   (  lock1  .   tryLock   (   3000   ,  TimeUnit  .  MICROSECONDS  )   )   {  System  .  out  .   println   (   "线程2获取到了锁1  "   )   ;  System  .  out  .   println   (   " 线程2 成功获取了两把锁   "   )   ;   //释放两把锁, 退出循环  lock1  .   unlock   (   )   ;  lock2  .   unlock   (   )   ;   break   ;   }   else   {  System  .  out  .   println   (   " 线程2尝试获取锁1 失败, 已经重试  "   )   ;   //释放锁2  lock2  .   unlock   (   )   ;   //随机的休眠  Thread  .   sleep   (   new   Random   (   )   .   nextInt   (   1000   )   )   ;   }   }   else   {  System  .  out  .   println   (   " 线程2 获取锁2失败, 已重试  "   )   ;   }   }   catch   (   InterruptedException  e  )   {  e  .   printStackTrace   (   )   ;   }   }   }   }   }   

    运行程序后, 此时打印的情况如下: 
    线程1和2 ,分别拿到了锁1 和2 . 如果此时是用 synchronized 加锁的, 那么就会进入死循环的情况 , 因为 此时线程1是要去获取锁2的, 而此时锁2被线程2持有着 , 线程2此时要获取锁1 ,而锁1被线程2持有, 那么就会造成死锁. 
    而使用trylock后, 如下图打印, 线程1在尝试800ms获取锁2失败后, 释放了锁1, 那么此时锁2就获得了锁1, 线程2获得了两把锁, 释放了这两把锁, 接着线程1就获得了这两把锁. 
     
    再次运行程序, 此时程序打印如下 . 可以看到线程2两次获取锁1 失败 , 两次获得了CPU的执行权, 可能是由于线程1休眠时间过长导致的. 
    线程2重复2次失败获取锁1失败后, 线程1苏醒, 获得了2把锁, 并且释放了两把锁, 线程2之后也获得了2把锁. 

    2. 多使用JUC包提供的并发类,而不是自己设计锁

    JDK1.5后, 有JUC包提供并发类, 而不需要自己用wait 和notify来进行线程间的通信操作 , 这些成熟的并发类已经考虑的场景很完备了, 比自己设计锁更加安全. 
    JUC中的并发类 例如 ConcurrentHashMap ConcurrentLinkedQueue AtomicBoolean 等等 
    实际应用中 java.util.concurrent.atomic 包中提供的类使用广泛, 简单方便, 并且效率比Lock更高.

    多用并发集合, 而不是用同步集合. 
    例如用 ConcurrentHashMap , 而不是使用下图中 Collections 工具类提供的同步集合. 因为同步集合性能低 

    3. 尽量降低锁的使用粒度

    尽量降低锁的使用粒度 : 用不同的锁 ,而不是同一个锁. 
    整个类如果使用一个锁来保护的话, 那么效率会很低, 而且有死锁的风险, 很多线程都来用这把锁的话, 就容易造成死锁. 
    锁的使用范围, 只要能满足业务要求, 范围越小越好.雅思5.5是什么水平

    4. 尽量使用同步方法 而不是同步代码块

    如果能使用同步代码块, 就不要使用同步方法, 
    好处有两点 :

    1. 同步方法是把整个方法给加上锁给同步了, 范围较大,造成性能低下, 使用同步代码块范围小,性能高.
    2. 使用同步代码块, 可以自己指定锁的对象, 这样有了锁的控制权, 这样也能避免发生死锁

    5. 给线程起有意义的名字

    给线程起有意义的名字, 是便于在测试环境和生产环境排查bug和事故的时候快速定位问题. 
    一些开源的框架和JDK都遵循了给线程起名字的规范

    6. 避免锁的嵌套

    如下的文章<必然发生死锁>例子中的代码就是锁的嵌套. 拿一个锁, 接着再拿一个锁. 并且使用的还是sleep这种不会释放锁的方式, 即拿到一个锁之后,不会去释放锁. 
    那么如果获取锁的顺序相反了, 就会造成死锁的发生! 
    https://javaweixin6.blog.csdn.net/article/details/108460550 

    7. 分配锁资源之前先看能不能收回来资源

    分配锁资源之前先看能不能收回来资源: 即在分配给某个线程锁资源之前, 先计算一下如果分配出去了, 会不会造成死锁的情况, 也就是能不能回收得回来, 如果不能回收回来, 那么就会造成死锁, 那就不分配锁资源给这个线程 , 如果能回收回来, 那么就分配资源下去.

    此种思想的实现有 银行家算法 来避免死锁的发生. 可以参考如下的文章 
    https://blog.csdn.net/u014634576/article/details/52600826

    https://mp.weixin.qq.com/s?__biz=MzAwNzczMjk1NQ==&mid=400637315&idx=1&sn=f578bf6de58c1a57df07df310ae1ca1b&scene=1&srcid=0920DQXmm3IeDGyaJxxLz6oZ#wechat_redirect

    https://www.cnblogs.com/128-cdy/p/12188340.html

    8. 专锁专用

    尽量不要几个功能用同一把锁. 来避免锁的冲突, 如果都用同一把锁, 那么就容易造成死锁.

  • 相关阅读:
    共享纸巾更换主板代码分析 共享纸巾主板更换后的对接代码
    Python Django Ajax 传递列表数据
    Python Django migrate 报错解决办法
    Python 创建字典的多种方式
    Python 两个list合并成一个字典
    Python 正则 re.sub替换
    python Django Ajax基础
    Python Django 获取表单数据的三种方式
    python Django html 模板循环条件
    Python Django ORM 字段类型、参数、外键操作
  • 原文地址:https://www.cnblogs.com/huilixieqi/p/13755145.html
Copyright © 2011-2022 走看看