天天看点

Java线程面试题(02) Java线程中如何避免死锁

本文为本博主翻译,未经允许,严禁转载!

简介

如何避免Java中的死锁?是多线程编程常见问题之一,在高级别的面试中经常被问及,并带来了大量的后续问题。尽管问题看起来很基本,但是一旦开始深入,大部分开发者都会陷入困境。

面试问题从“什么是死锁?”开始。答案很简单,当有两个或更多的线程在等待对方释放锁并无限期地卡住时,这种情况就称为死锁。这只会发生在多线程的情况下。

Java中如何监测死锁

虽然这可能有很多的答案,我的版本是首先我会看代码,如果我看到嵌套的同步块或从其他调用一个同步方法或尝试锁定不同的对象,那么如果开发人员不是非常小心,很有可能发生死锁。

其他方法是在运行应用程序时实际上被锁定的时候找到它,试着对线程进行转储,在Linux中你可以通过命令“kill -3”来做到这一点,这将在应用程序日志文件中打印所有线程的状态,而你可以看到哪个线程被锁定在哪个对象上。

其他的方法是使用jconsole,它会告诉你究竟哪个线程被锁定在哪个对象上。

编写一个会导致死锁的Java程序

一旦你回答了这个问题,他们可能会要求你编写会导致死锁的代码?这是我的一个版本。

/**
 * * Java program to create a deadlock by imposing circular wait. * * @author
 * WINDOWS 8 *
 */
public class DeadLockDemo {
	/* 
	 * This method request two locks, first String and then Integer 
	 **/
	public void method1() {
		synchronized (String.class) {
			System.out.println("Aquired lock on String.class object");
			synchronized (Integer.class) {
				System.out.println("Aquired lock on Integer.class object");
			}
		}
	}

	/*
	 * * This method also requests same two lock but in exactly * Opposite order
	 * i.e. first Integer and then String. * This creates potential deadlock, if
	 * one thread holds String lock * and other holds Integer lock and they wait
	 * for each other, forever.
	 */
	public void method2() {
		synchronized (Integer.class) {
			System.out.println("Aquired lock on Integer.class object");
			synchronized (String.class) {
				System.out.println("Aquired lock on String.class object");
			}
		}
	}
}
           

如果method1()和method2()都被两个或多个线程调用,死锁就很有可能,因为如果线程1在执行method1()时获取对Sting对象的锁,并且线程2在执行method2()时获取对Integer对象的锁, 那么这两个线程都将在等待对方释放Integer和String锁,这将永远不会发生。

这个图完全展示了我们的程序,其中一个线程持有一个对象的锁,并等待其他线程持有的其他对象锁。

Java线程面试题(02) Java线程中如何避免死锁

如何避免Java中的死锁?

现在面试官来到最后一部分,我认为最重要的部分之一: 你如何解决死锁?或者如何避免Java中的死锁?

如果你仔细看过上面的代码,那么你可能已经发现死锁的真正原因并不是多线程,而是它们请求锁定的方式,如果你提供了一个有序的访问,那么问题就会解决,这里是我的修复死锁问题的版本,它可以通过在没有抢占的情况下避免循环等待从而解决死锁问题。

public class DeadLockFixed {
	/**
	 * * Both method are now requesting lock in same order, first Integer and
	 * then String. * You could have also done reverse e.g. first String and
	 * then Integer, * both will solve the problem, as long as both method are
	 * requesting lock * in consistent order.
	 */
	public void method1() {
		synchronized (Integer.class) {
			System.out.println("Aquired lock on Integer.class object");
			synchronized (String.class) {
				System.out.println("Aquired lock on String.class object");
			}
		}
	}

	public void method2() {
		synchronized (Integer.class) {
			System.out.println("Aquired lock on Integer.class object");
			synchronized (String.class) {
				System.out.println("Aquired lock on String.class object");
			}
		}
	}
}
           

现在不会有任何死锁,因为两个方法都以相同的顺序访问Integer和String类的锁。因此,如果线程A获得了Integer对象上的锁,则线程B将不会继续,直到线程A释放Integer锁定为止,即使线程B持有String锁,线程A也不会被阻塞,因为现在线程B不会期望线程A释放Integer锁继续前进。

原文链接

How to avoid deadlock in Java Threads