首页 > 编程笔记

什么是死锁,Java死锁详解

使用 synchronized 可以实现线程同步,这可以解决多线程并行访问数据带来的安全问题。但是任何事物都没有绝对的好与坏,synchronized 在解决线程安全问题的同时也会带来一个隐患,并且这个隐患是比较严重的,那就是死锁。

先来解释一下死锁的概念,举个生活中的例子,10 个人围一桌吃饭,但是每个人只有一根筷子,要求必须凑齐一双筷子才可以吃菜。也就是说每个人必须要拿到其他人的筷子,但是每个人又都不愿意把自己的筷子让给别人,都在等待其他人主动把筷子贡献出来。这样就形成了一个死局,如果一直保存这种状态,这个饭局就一直僵在这里,没有人可以吃到菜,这就是死锁。

如果把每个人看成一个线程,筷子就是线程要获取的资源,现在每个线程都占用一个资源并且不愿意释放,而且任意一个线程想继续执行就必须获取其他线程的资源,那么所有的线程都处于阻塞状态,程序无法向下执行也无法结束。

如何破解死锁呢?唯有某个线程愿意作出让步,贡献出自己的资源给其他线程使用。获取到资源的线程就可以执行自己的业务方法,执行完毕后会释放它所占用的两个资源,其他线程就可以依次获取资源来执行业务方法,问题就迎刃而解了。

相当于在那个僵持的饭局上,有一个人愿意作出牺牲,把自己的筷子让给身边的人,这样他身边的人就可以吃饭了,待他吃饱之后,会把他的筷子贡献出来,这样所有人都可以依次吃到饭了。

我们通过一段代码来演示死锁的情况:
public class Chopsticks {
}

public class DeadLockRunnable implements Runnable{
    public int num;
    private static Chopsticks chopsticks1 = new Chopsticks();
    private static Chopsticks chopsticks2 = new Chopsticks();
    @Override
    public void run() {
        // TODO Auto-generated method stub
        if(num == 1){
            System.out.println(Thread.currentThread().getName()+"获取到chopsticks1,等待获取chopsticks2");
            synchronized (chopsticks1) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (chopsticks2) {
                    System.out.println(Thread.currentThread().getName()+"用餐完毕");
                }
            }
        }
        if(num == 2){
            System.out.println(Thread.currentThread().getName()+"获取到chopsticks2,等待获取chopsticks1");
            synchronized (chopsticks2) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (chopsticks1) {
                    System.out.println(Thread.currentThread().getName()+"用餐完毕");
                }
            }
        }
    }
}

public class DeadLockTest {
    public static void main(String[] args) {
        DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable();
        deadLockRunnable1.num = 1;
        DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable();
        deadLockRunnable2.num = 2;
        new Thread(deadLockRunnable1,"张三").start();
        new Thread(deadLockRunnable2,"李四").start();
    }
}
运行程序,结果如下图所示:


图 1 发生死锁

张三获取了资源 chopsticks1,必须同时获取 chopsticks2 才能完成用餐,但是 chopsticks2 被李四占用,李四只有完成用餐才能释放 chopsticks2,但是李四完成用餐的必要条件是获取 chopsticks1,所以形成了一种互斥的关系,这就是死锁。

可以对代码进行修改,不要让两个线程并行访问,先启动代表张三的线程,休眠 2000ms,待张三完成用餐释放资源之后再启动代表李四的线程,修改后的代码为:
public class DeadLockTest {
    public static void main(String[] args) {
        DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable();
        deadLockRunnable1.num = 1;
        DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable();
        deadLockRunnable2.num = 2;
        new Thread(deadLockRunnable1,"张三").start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {
        }
        new Thread(deadLockRunnable2,"李四").start();
    }
}
运行程序,结果为:

张三获取到chopsticks1,等待获取chopsticks2
张三用餐完毕
李四获取到chopsticks2,等待获取chopsticks1
李四用餐完毕

这样就可以解决死锁导致的问题。

实际上死锁是一种错误,在实际开发中需要注意避免这种错误的出现。

推荐阅读