Java 线程死锁的产生以及避免

线程死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源不放弃,等待对方先释放自己需要的同步资源,导致这些线程处于阻塞状态,无法继续执行。

public class DeadLockDemo {
    private static Object resource1 = new Object();
    private static Object resource2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);//thread1不释放recourse1,休眠1秒,让thread2去获得resource2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting resource2");
                synchronized (resource2) {//thread1不放弃resource1,又想获得resource2
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "thread1");
        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {//thread2获得resource2
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "thread2");
        thread1.start();
        thread2.start();
    }
}

Java 线程死锁的产生以及避免_第1张图片由输出结果可以看出,thread1获得了resource1,thread获得了resource2,thread1想要获取resouce2,thread2想要获取resouce1,但是两个线程都不先释放自己的锁资源,导致这两个线程进入阻塞状态,这两个线程死锁了。

线程死锁的四个必要条件:
  1. 互斥条件:一段时间内某资源仅为一个进程所占。
  2. 不可剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。
  3. 请求和保持条件:线程已经拥有(保持)了一个资源不释放,又去请求新的资源,而该新资源被其他线程拥有不释放,导致线程阻塞。
  4. 循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。
如何避免线程死锁

破坏线程死锁的条件:

  1. 破坏互斥条件:这个条件没有办法破坏,因为锁本来就是想让他们互斥的(临界资源需要互斥访问)。
  2. 破坏不可剥夺条件:⼀次性申请所有的资源。
  3. 破坏请求和保持条件:占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  4. 破坏循环等待条件:靠按序申请资源来预防。按某⼀顺序申请资源,释放资源则反序释放。

例如:

public class DeadLockDemo1 {

    private static Object resource1 = new Object();
    private static Object resource2 = new Object();
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }

        }, "thread1");
        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting resource1");
            }
            synchronized (resource1) {//破坏了请求和保持条件,thread2释放了resource2,再请求resource1
                System.out.println(Thread.currentThread() + "get resource1");
            }
        }, "thread2");
        thread1.start();
        thread2.start();
    }
}

输出结果:
Java 线程死锁的产生以及避免_第2张图片thread2主动释放了resouce2,再去请求resouce1,让thread1可以获得resource2,thread1执行完后释放resource1、resource2,这样thread2就可以获得resource1了,这就是破坏了请求和保持条件。

再看下面代码:

public class DeadLockDemo2 {
    private static Object resource1 = new Object();
    private static Object resource2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);//thread1不释放recourse1,休眠1秒,让thread2获得resource2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting resource2");
                synchronized (resource2) {//thread1不放弃resource1,又想获得resource2
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "thread1");
        Thread thread2 = new Thread(() -> {
            synchronized (resource1) {//thread2获得resource2
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting resource1");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "thread2");
        thread1.start();
        thread2.start();
    }
}

输出结果:
Java 线程死锁的产生以及避免_第3张图片thread1和thread2都需要resource1,thread1先获取了resouce1,获取不到resource代码不会执行,thread1醒来后再去请求resouce2,thread1执行完后释放resource1、resource2,这样thread2就可以获得resource1了,这就是破坏了循环等待条件。

补充:sleep(long)和wait()和wait(long)
  1. sleep(long)不释放锁休眠;wait()和wait(long)都是释放锁休眠。
  2. sleep(long)是Thread类的静态方法;wait()、wait(long)、notify()、notifyAll()是Object类的成员方法。
  3. sleep(long)、wait(long)经过long时间后会自动醒来;调用wait()线程不会自动醒来,需要被其他线程内的同一对象用notifyAll()或者notify()唤醒。

以下代码中两个线程也不会死锁:

public class DeadLockDemo {
    private static Object resource1 = new Object();
    private static Object resource2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    //Thread.sleep(1000);//sleep()不放弃锁休眠,thread1不释放recourse1,休眠1秒,让thread2获得resource2
                    //resource1.wait(1000);//wait(1000)暂时放弃resource1,1s后自动醒来
                    resource1.wait();//wait()暂时放弃锁休眠,由notify()和notifyAll()方法唤醒。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting resource2");
                synchronized (resource2) {//thread1不放弃resource1,又想获得resource2
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "thread1");
        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {//thread2获得resource2
                System.out.println(Thread.currentThread() + "get resource2");
                System.out.println(Thread.currentThread() + "waiting resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                    resource1.notifyAll();
                }

            }
        }, "thread2");
        thread1.start();
        thread2.start();
    }
}

运行结果:
Java 线程死锁的产生以及避免_第4张图片在线程1中用resource1.wait()方法,需要在线程2中调用resource1.notifyAll()来唤醒线程1。若用resourcr1.wait(1000),那么1秒后线程会自己醒来。

你可能感兴趣的:(Java,java,面试)