关于线程死锁问题

Posted 我是攻城师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于线程死锁问题相关的知识,希望对你有一定的参考价值。

前言

死锁是多线程编程里面非常常见的一个问题,作为一个中高级开发者是必须掌握的内容,今天我们来学习一下死锁相关的知识。

什么是死锁

死锁指的是两个线程都需要获得锁,但是它们之间又成环引用,从而造成程序无限等待永远不会终止。

看下面一个demo示例:

 
   
   
 
  1.    public  void m1()throws Exception{

  2.        //lock1

  3.        synchronized (String.class){

  4.            //lock2

  5.            synchronized (Integer.class){

  6.            }

  7.        }

  8.    }

  9.    public  void m2()throws Exception{

  10.        //lock1

  11.        synchronized (Integer.class){

  12.            //lock2

  13.            synchronized (String.class){

  14.            }

  15.        }

  16.    }

上面的示例中,有两个方法分别是m1和m2,假设这里有两个线程分别同时调用了m1和m2,那么这里就会有非常大的概率形成死锁,我们来简单推导一下它是如何发生的:

线程A调用了m1方法,拿到了String类的监视器,并进入了lock1的同步块,同时线程B调用了m2方法,拿到了Integer类的监视器,也进入了lock1的同步块,接下来线程A要进入lock2的同步块,但是它进不去因为synchronized方法只能有一个线程进入临界区,必须等待线程B释放了Integer监视器的锁,线程A才能继续执行,但线程B恰恰也需要线程A释放了String监视器才能释放Integer的锁,这样以来他们就形成了环路,谁都在等待对方释放锁,这样以来他们永远就会处于BLOCK状态,从而造成了死锁的问题。

这里有一个实际开发中典型的案例,在银行转账时候,比如有两个人A和B,A要转账给B,那么首先给A加锁,保证同一时候对A的操作永远只有一个人,然后调用A的扣款方法,接着要对B加锁,保证同一时候只有一个人操作B的账户,接着调用B的存款方法,看起很完美,但是如果同时B也对A转账,那么就可能形成死锁。

如何发现死锁

当程序发生死锁的时候,程序还处于运行状态,只不过线程被阻塞,可能应用程序会被卡住,这时候我们就需要通过一些手段来发现,常用的方法有:

(1)在win上打开cmd命令,输入jconsole,visualvm,jmc三者任意一个命令,都能打开相关的界面工具,在线程面板中我们可以非常轻而易举的找到死锁,如果找不到可以使用工具提供的死锁检测按钮来分析。

(2)在linux上也有一些方法,不管使用哪种方法,我们需要首先知道程序的进程id,这个可以执行jps -lvm命令来找到:

方法一使用kill -3 java_pid 这个命令并不会杀死Java程序而仅仅在终端到以标准输出流的方式,到处线程的dump信息。

方法二使用jdk自带的jstack命令,执行jstack java_pid 导出线程的dump信息之后,可以找到程序是否有死锁

如何避免死锁

关于避免死锁,这里有几个重要的实践经验:

(1)死锁的根源在于有多个同步锁存在,那么最好的解决方法就是没有锁的出现,就不会有死锁的问题或者使用Java并发包里面无锁的数据结构,如ConcurrentLinkedQueue,volatile,atom变量等,从而避免从根源上死锁问题。

(2)如果真的不能避免同步,必须使用锁,那么这里有一个重要的方法,就是保证两个线程锁的顺序是一致的,这样就不会出现死锁,比如第一个例子,如果改造成下面的代码就可以避免死锁:

 
   
   
 
  1. // method1

  2. m1 {

  3. // lock1

  4. sync(String.class){

  5. //lock2

  6. sync(Integer.class){

  7. }

  8. }

  9. }

  10. // method2

  11. m2 {

  12. // lock1

  13. sync(String.class){

  14. //lock2

  15. sync(Integer.class){

  16. }

  17. }

  18. }

知道这个规则之后,如银行的转账的例子,只需要根据账户的id排序,形成一个固定的顺序的嵌套锁,那么就可避免死锁的问题。

(3)如果仍然无法保证复杂的程序是否会有死锁的问题,那么我可以使用jdk5之后新的并发包里面的超时锁,这个不是避免问题, 但是可以减少死锁发生后影响,如果在一段时间内没有响应,就会超时自动释放自己持有的锁,从而在一定程度上减少死锁对应用的影响。


总结

本文主要介绍了Java里面关于线程死锁的问题,首先介绍了什么是死锁,然后讲了如何发现死锁,最后我们总结了如何避免死锁,这些内容对一个高级的开发者来说是必不可少的基本知识,掌握了这些将更加有助于编写具有更多鲁棒性的多线程程序。文中死锁的完整例子,已经上传到我的github上,这个项目包含了很多的Java相关的典型问题示例,感兴趣的朋友可以学习和了解一下。

https://github.com/qindongliang/Java-Note


以上是关于关于线程死锁问题的主要内容,如果未能解决你的问题,请参考以下文章

关于线程死锁问题

对一个死锁问题的思考

java synchronized 死锁问题

关于死锁的实现与java问题定位

操作系统关于多线程同步中的死锁问题一篇文章让你彻底搞明白死锁到底是什么情况及如何解决死锁

JAVA程序设计,多线程且避免死锁