java 多线程 - 死锁

前言java

死锁单独写一篇文章是由于这是一个很严重的、必需要引发重视的问题。这不是夸大死锁的风险,尽管锁被持有的时间一般很短,可是做为商业产品的应用程序天天可能要执行数十亿次获取锁->释放锁的操做,只要在这数十亿次操做中只要有一次发生了错误,就可能致使程序中发生死锁,而且即便经过压力测试也不可能找出全部潜在的死锁。数据库

 

死锁多线程

一个经典的多线程问题。函数

当一个线程永远地持有一个锁,而且其余线程都尝试去得到这个锁时,那么它们将永远被阻塞,这个咱们都知道。若是线程A持有锁L而且想得到锁M,线程B持有锁M而且想得到锁L,那么这两个线程将永远等待下去,这种状况就是最简单的死锁形式。测试

在数据库系统的设计中考虑了监测死锁以及从死锁中恢复,数据库若是监测到了一组事物发生了死锁时,将选择一个牺牲者并放弃这个事物。Java虚拟机解决死锁问题方面并无数据库这么强大,当一组Java线程发生死锁时,这两个线程就永远不能再使用了,而且因为两个线程分别持有了两个锁,那么这两段同步代码/代码块也没法再运行了----除非终止并重启应用。this

死锁是设计的BUG,问题比较隐晦。不过死锁形成的影响不多会当即显现出来,一个类可能发生死锁,并不意味着每次都会发生死锁,这只是表示有可能。当死锁出现时,每每是在最糟糕的状况----高负载的状况下线程

下面给出一个产生死锁的简单代码而且演示如何分析这是一个死锁:设计

public class DeadLock
{
    private final Object left = new Object();
    private final Object right = new Object();
    
    public void leftRight() throws Exception
    {
        synchronized (left)
        {
            Thread.sleep(2000);
            synchronized (right)
            {
                System.out.println("leftRight end!");
            }
        }
    }
    
    public void rightLeft() throws Exception
    {
        synchronized (right)
        {
            Thread.sleep(2000);
            synchronized (left)
            {
                System.out.println("rightLeft end!");
            }
        }
    }
}

 

注意这里必定要有"Thread.sleep(2000)"让线程睡一觉,否则一个线程运行了,另外一个线程尚未运行,先运行的线程颇有可能就已经连续得到两个锁了。写两个线程分别调用它们:进程

public class Thread0 extends Thread
{
    private DeadLock dl;
    
    public Thread0(DeadLock dl)
    {
        this.dl = dl;
    }
    
    public void run()
    {
        try
        {
            dl.leftRight();
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

 

public class Thread1 extends Thread
{
    private DeadLock dl;
    
    public Thread1(DeadLock dl)
    {
        this.dl = dl;
    }
    
    public void run()
    {
        try
        {
            dl.rightLeft();
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

 

写个main函数调用一下:ip

public static void main(String[] args)
{
    DeadLock dl = new DeadLock();
    Thread0 t0 = new Thread0(dl);
    Thread1 t1 = new Thread1(dl);
    t0.start();
    t1.start();

    while(true);   
}

 

至于结果,没有结果,什么语句都不会打印,由于死锁了。下面演示一下如何定位死锁问题:

一、jps得到当前Java虚拟机进程的pid

 

二、jstack打印堆栈。jstack打印内容的最后其实已经报告发现了一个死锁,但由于咱们是分析死锁产生的缘由,而不是直接获得这里有一个死锁的结论,因此别管它,就看前面的部分

先说明介绍一下每一部分的意思,以"Thread-1"为例:

(1)"Thread-1"表示线程名称

(2)"prio=6"表示线程优先级

(3)"tid=00000000497cec00"表示线程Id

(4)nid=0x219c

线程对应的本地线程Id,这个重点说明下。由于Java线程是依附于Java虚拟机中的本地线程来运行的,其实是本地线程在执行Java线程代码,只有本地线程才是真正的线程实体。Java代码中建立一个thread,虚拟机在运行期就会建立一个对应的本地线程,而这个本地线程才是真正的线程实体。Linux环境下可使用"top -H -p JVM进程Id"来查看JVM进程下的本地线程(也被称做LWP)信息,注意这个本地线程是用十进制表示的,nid是用16进制表示的,转换一下就行了,0x219c对应的本地线程Id应该是8604。

(5)"[0x000000004a3bf000..0x000000004a3bf790]"表示线程占用的内存地址

(6)"java.lang.Thread.State:BLOCKED"表示线程的状态

解释完了每一部分的意思,看下Thread-1处于BLOCKED状态,Thread-0处于BLOCKED状态。对这两个线程分析一下:

(1)Thread-1得到了锁0x000000003416a4e8,在等待锁0x000000003416a4d8

(2)Thread-0得到了锁0x000000003416a4d8,在等待锁0x000000003416a4e8

因为两个线程都在等待获取对方持有的锁,因此就这么永久等待下去了。

三、注意一下使用Eclipse/MyEclipse,这段程序若是不点击控制台上面的红色方框去Terminate掉它,而是右键->Run As->1 Java Application的话,这个进程会一直存在的,这时候能够利用taskkill命令去终止没有被Terminate的进程:

 

避免死锁的方式

既然可能产生死锁,那么接下来,讲一下如何避免死锁。

一、让程序每次至多只能得到一个锁。固然,在多线程环境下,这种状况一般并不现实

二、设计时考虑清楚锁的顺序,尽可能减小嵌在的加锁交互数量

三、既然死锁的产生是两个线程无限等待对方持有的锁,那么只要等待时间有个上限不就行了。固然synchronized不具有这个功能,可是咱们可使用Lock类中的tryLock方法去尝试获取锁,这个方法能够指定一个超时时限,在等待超过该时限以后变回返回一个失败信息

相关文章
相关标签/搜索