Java并发编程如何防止死锁?

22-09-23 banq

死锁可能是大家都不想遇到的问题,因为一旦程序出现死锁,如果没有外力的话,程序会因为资源竞争一直处于假死状态。
死锁示例代码如下:

public class DeadLockTest {

    public static String OBJECT_1 = "OBJECT_1";
    public static String OBJECT_2 = "OBJECT_2";

    public static void main(String[] args) {
        LockA lockA = new LockA();
        new Thread(lockA).start();

        LockB lockB = new LockB();
        new Thread(lockB).start();
    }

}

class LockA implements Runnable {

    @Override
    public void run() {
        synchronized (DeadLockTest.OBJECT_1) {
            try {
                Thread.sleep(500);

                synchronized (DeadLockTest.OBJECT_2) {
                    System.out.println("LockA");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class LockB implements Runnable {

    @Override
    public void run() {
        synchronized (DeadLockTest.OBJECT_2) {
            try {
                Thread.sleep(500);

                synchronized (DeadLockTest.OBJECT_1) {
                    System.out.println("LockB");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

当一个线程获得了 OBJECT_1 锁时,它并没有释放锁,然后再申请 OBJECT_2 锁。
这时,另一个线程获取了OBJECT_2锁,并没有释放锁去申请OBJECT_1锁。
由于 OBJECT_1 和 OBJECT_2 锁都没有被释放,所以两个线程会一起请求,陷入死循环,即出现死锁情况。

那么如果避免了死锁问题呢?

# 1. 缩小锁的范围。
死锁情况可能是如上锁范围过大造成的。
那么,解决方案就是缩小锁的范围。

class LockA implements Runnable {

    @Override
    public void run() {
        synchronized (DeadLockTest.OBJECT_1) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        synchronized (DeadLockTest.OBJECT_2) {
             System.out.println("LockA");
        }
    }
}

class LockB implements Runnable {

    @Override
    public void run() {
        synchronized (DeadLockTest.OBJECT_2) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        synchronized (DeadLockTest.OBJECT_1) {
             System.out.println("LockB");
        }
    }
}

在获取 OBJECT_1 锁的代码块中,不包含获取 OBJECT_2 锁的代码。
同时,获取OBJECT_2锁的代码块中不包含获取OBJECT_1锁的代码。

# 2. 保证锁的顺序。
在死锁的情况下,线程获取锁的顺序是OBJECT_1和OBJECT_2。
另一个线程以相反的顺序获取锁:OBJECT_2 和 OBJECT_1。
那么,如果我们能保证每次获取锁的顺序都是一样的,就不会出现死锁问题。

class LockA implements Runnable {

    @Override
    public void run() {
        synchronized (DeadLockTest.OBJECT_1) {
            try {
                Thread.sleep(500);

                synchronized (DeadLockTest.OBJECT_2) {
                    System.out.println("LockA");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class LockB implements Runnable {

    @Override
    public void run() {
        synchronized (DeadLockTest.OBJECT_1) {
            try {
                Thread.sleep(500);

                synchronized (DeadLockTest.OBJECT_2) {
                    System.out.println("LockB");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

两个线程,每个线程先获取 OBJECT_1 锁,然后再获取 OBJECT_2 锁。