关于线程死锁问题

前言

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

什么是死锁

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

看下面一个demo示例:

public  void m1()throws Exception{

        //lock1
        synchronized (String.class){

            //lock2
            synchronized (Integer.class){

            }

        }


    }

    public  void m2()throws Exception{

        //lock1
        synchronized (Integer.class){

            //lock2
            synchronized (String.class){

            }

        }


    }

上面的示例中,有两个方法分别是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)如果真的不能避免同步,必须使用锁,那么这里有一个重要的方法,就是保证两个线程锁的顺序是一致的,这样就不会出现死锁,比如第一个例子,如果改造成下面的代码就可以避免死锁:

// method1

m1 {

// lock1
sync(String.class){

//lock2
sync(Integer.class){

}

}

}

// method2

m2 {

// lock1
sync(String.class){

//lock2
sync(Integer.class){

}

}

}

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

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

总结

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

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

原文发布于微信公众号 - 我是攻城师(woshigcs)

原文发表时间:2018-07-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户2442861的专栏

linux工作中常用文件操作命令

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/d...

28020
来自专栏资深Tester

一定要知道的,那些Linux操作命令(二)

22860
来自专栏开源优测

python selenium - 利用excel实现参数化

前言 在进行软件测试或设计自动化测试框架时,一个比可避免的过程就是: 参数化,在利用python进行自动化测试开发时,通常会使用excel来做数据管理,利用xl...

30270
来自专栏张戈的专栏

Shell脚本的简单排错法及调试程序bashdb

Jboss 的研究稍有卡壳,那就来点基础教程好了。 与众多脚本语言一样,Shell 脚本在执行时出错是很常见的,最简单的原因无外乎脚本在编写的过程中出现了语法错...

40960
来自专栏Java技术栈

dubbo服务调试管理实用命令

公司如果分项目组开发的,各个项目组调用各项目组的接口,有时候需要在联调环境调试对方的接口,可以直接telnet到dubbo的服务通过命令查看已经布的接口和方法,...

39670
来自专栏北京马哥教育

Python爬虫基础知识:urllib2的使用技巧

糖豆贴心提醒,本文阅读时间6分钟 前面说到了urllib2的简单入门,下面整理了一部分urllib2的使用细节。 1.Proxy 的设置 urllib2 默认...

31450
来自专栏逆向与安全

GDB多线程调试分析

多线程调试的主要任务是准确及时地捕捉被调试程序线程状态的变化的事件,并且GDB针对根据捕捉到的事件做出相应的操作,其实最终的结果就是维护一根叫thread li...

13700
来自专栏深度学习之tensorflow实战篇

交互式使用 R题(shell)

交互式使用 R 交互式shell是一种很方便的环境,可以进行各种尝试,随时调整过程。与Python、Ruby等语言一样,R也提供了shell环境。本文开始的例子...

31550
来自专栏AzMark

Python 学习之进程与线程 「 上 」

进程:对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器(任务)就是启动一个浏览器进程。进程是系统中程序执行和资源分配的基本单位,每个...

7620
来自专栏黑泽君的专栏

用gcc编译c语言程序以及其编译过程

对于初学c语言编程的我们来说,学会如何使用gcc编译器工具,对理解c语言的执行过程,加深对c语言的理解很重要!!!

17110

扫码关注云+社区

领取腾讯云代金券