线程死锁

我们知道线程长时间停顿的主要原因有:等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待(死锁和活锁)。

现在写一个线程死锁的小Demo,就我自己的理解就是:线程t1先锁定一个对象A,再锁定对象B。而线程t2先锁定对象B,再锁定对象A。当线程t1先执行时,并没有释放锁对象B,但是线程t2要锁定对象B(线程t1并没有释放锁),同理,要是线程t2先执行,并没有释放锁对象A,但是线程t1要锁定对象A(线程t2并没有释放锁),此时就形成锁的相互争夺,相互不释放,造成线程死锁问题。

下面来看一个小Demo


public class DeadLockTest {

    static class DeadLock implements Runnable{

        int a,b;
        public DeadLock(int a,int b) {
            this.a=a;
            this.b=b;
        }

        public void run() {
            synchronized (Integer.valueOf(a)) {
                synchronized (Integer.valueOf(b)) {
                    sum(a, b);
                }
            }
        }

        int sum(int a,int b){
            return a+b;
        }

    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new DeadLock(1, 2)).start();
            new Thread(new DeadLock(2, 1)).start();
        }

    }

}

这段代码开了200个线程去计算1+2的值,其实不用for循环也会造成死锁,只是概率比较小,要是很多次才能成功(因为循环时一个线程的执行时间比较短,线程的锁已经释放,所以要试很多次才能看到死锁的现象,要是想一次看到死锁的效果,可以让线程等待,下面会讲到)。造成死锁的原因是Integer.valueOf()方法基于减少对象创建次数和节省内存考虑,[-128,127]之间的数字已经被缓存,当valueOf()输入的数字在这个范围内,则直接返回缓存的对象。

也就是说,代码中跑了200次Integer.valueOf()方法,只返回了两个不同的对象。加入在某个线程块的sychronized块之间发生了一次线程切换,那就会出现线程t1线程t2拥有的Integer.valueOf(1),线程t2线程t1拥有的Integer.valueOf(2),就会出现跑不下去的情况。

我们用jConsole工具来检测线程的死锁,会得到有关死锁的相关信息。
在此处查看线程死锁
线程死锁的详细信息
这里写图片描述
这里写图片描述

由图中可知线程3 4 199造成死锁,其中线程4的锁在线程3中锁定,线程4和199的锁在3中锁定。

在跑这个程序的时候会出现,跑多次都没有造成死锁现象,解决此问题要在线程睡眠(下面会说到),所以要多跑几次,才能看到死锁的现象。

还有一点奇怪的是每次跑线程199都会出现死锁。不知道为什么固定是线程199

为了解决上面跑了几次没有出现死锁的现象,另外写了一个小Demo


public class DeadLockTest1 {

    private static String A="A";

    private static String B="B";

    private static void deadLock(){
        Thread t1=new Thread(new Runnable() {

            public void run() {
                synchronized (A) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    synchronized (B) {
                        System.out.println("我是线程t1");
                    }
                }
            }
        },"t1");

        Thread t2=new Thread(new Runnable() {

            public void run() {
                synchronized (B) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    synchronized (A) {
                        System.out.println("我是线程t2");
                    }
                }
            }
        },"t2");
        t1.start();
        t2.start();
    }

    public static void main(String[] args) {
        deadLock();

    }

}

在JConsole中检测死锁,信息如下:
名称: t1
状态: java.lang.String@24aa663f上的BLOCKED, 拥有者: t2
总阻止数: 1, 总等待数: 1

名称: t2
状态: java.lang.String@1179bd59上的BLOCKED, 拥有者: t1
总阻止数: 1, 总等待数: 1

现在我们介绍避免死锁的几种常见方法
1、避免一个线程同时获取多个锁
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
3、尝试使用定时锁,使用lock.tryLock(timeout)来替代内部锁机制。
4、对于数据库锁,加锁和解锁必须在同一个数据库连接里,否则会出现解锁失败的情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值