Java多线程(五):死锁

本文深入探讨了死锁的概念,通过实例演示了线程在同时请求不同锁时如何陷入死锁状态,分析了死锁产生的原因及对系统的影响。文章还提供了死锁排查的方法,包括使用jps、jstack、JConsole和JavaVisualVM等工具,并提出了避免死锁的策略,如减少嵌套锁的使用、规定锁的获取顺序和使用Lock的tryLock方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

死锁

概念

当线程Thread-0持有锁Lock1,Thread-1持有锁Lock2,此时Thread-0申请Lock2锁的使用权,Thread-1申请Lock1锁的使用权,Thread-0和Thread-1都在无限地等待锁的使用权。这样就造成了死锁。
1201453-20190708165359115-1517655139.jpg
死锁是主要由于设计的问题。一旦出现死锁,死锁的线程就会永远不能使用,同步方法不会被执行,死锁线程不会被自动终止,无尽地消耗CPU资源。

例子

看一个例子
ThreadDomain29类,模拟图片中,线程持有一个锁,申请被其他线程持有的锁的情况

public class ThreadDomain29 {
    private final Object obj1 = new Object();
    private final Object obj2 = new Object();

    public void obj1obj2() throws Exception
    {
        synchronized (obj1)
        {
            Thread.sleep(2000);
            synchronized (obj2)
            {
                System.out.println("obj1obj2 end!");
            }
        }
    }

    public void obj2obj1() throws Exception
    {
        synchronized (obj2)
        {
            Thread.sleep(2000);
            synchronized (obj1)
            {
                System.out.println("obj2obj1 end!");
            }
        }
    }
}

MyThread29_0类

public class MyThread29_0 extends Thread{
    private ThreadDomain29 dl;

    public MyThread29_0(ThreadDomain29 dl)
    {
        this.dl = dl;
    }

    public void run()
    {
        try
        {
            dl.obj1obj2();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

MyThread29_1类

public class MyThread29_1 extends Thread{
    private ThreadDomain29 dl;

    public MyThread29_1(ThreadDomain29 dl)
    {
        this.dl = dl;
    }

    public void run()
    {
        try
        {
            dl.obj2obj1();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

main方法

public class MyThread29_main {
    public static void main(String[] args)
    {
        ThreadDomain29 dl = new ThreadDomain29();
        MyThread29_0 t0 = new MyThread29_0(dl);
        MyThread29_1 t1 = new MyThread29_1(dl);
        t0.start();
        t1.start();

        while(true);
    }
}

因为发生了死锁,所以你不会看到任何结果。

死锁排查

jps+jstack

jps找到进程id
1201453-20190708171005756-1777676810.png
jstack打印堆栈
输入jstack 10208
1201453-20190708171154345-553126010.png
可以看到,找到了两个死锁。

JConsole

命令行输入JConsole
1201453-20190708171636651-736846558.png
点击“检测死锁”
1201453-20190708171716062-1355149342.png
已经检测到了死锁
1201453-20190708171747936-1289681493.png

Java Visual VM

命令行输入jvisualvm
找到我们的进程
1201453-20190708172153509-575939624.png
自动检测到死锁,推荐使用这种方式

死锁避免

1.尽量少用嵌套的锁。
2.如果一定要用嵌套锁,那么请规定好获取锁的顺序。例子如下:

//伪代码
//condition1可以是属性值大小,hash值大小的比较等等
if(condition1){
            synchronized (obj2)
            {
                Thread.sleep(2000);
                synchronized (obj1)
                {
                    System.out.println("obj2obj1 end!");
                }
            }
        }else{
            synchronized (obj1)
            {
                Thread.sleep(2000);
                synchronized (obj2)
                {
                    System.out.println("obj1obj2 end!");
                }
            }
        }

3.使用Lock的tryLock方法,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
后面讲到ReentrantLock会详细分析。

转载于:https://www.cnblogs.com/Java-Starter/p/11152705.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值