死锁是什么及在并发程序中如何避免死锁一直是面试官比较偏爱的问题,并且死锁是一个很严重的问题,必须要引起重视。
死锁:
当两个线程相互等待对方释放“锁”时就会发生死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。如果线程A持有锁L并且想获得锁M,线程C持有锁M并且想要获得锁L,那么这两个线程将永远等待下去,这就是简单的死锁形式。多线程编程时应该注意避免死锁的发生。
一个简单的示例帮助理解死锁:
package com.dsx.demo3;
public class LockDemo {
public static void main(String[] args) {
//线程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
LockDemo.method1();
}
});
//线程2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
LockDemo.method2();
}
});
t1.start();
t2.start();
}
public static void method1() {
synchronized(String.class) {
try {
Thread.sleep(2000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1尝试获取Integer.class");
synchronized(Integer.class) {
}
System.out.println("线程1已经获取Integer.class");
}
}
public static void method2() {
synchronized(Integer.class) {
try {
Thread.sleep(2000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2尝试获取String.class");
synchronized(String.class) {
}
System.out.println("线程2已经获取String.class");
}
}
}
运行结果:
在并发程序中,避免了逻辑中出现复数个线程互相持有对方线程所需要的独占锁的的情况,就可以避免死锁。
package com.dsx.demo3;
public class LockDemo {
public static void main(String[] args) {
//线程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
LockDemo.method1();
}
});
//线程2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
LockDemo.method2();
}
});
t1.start();
t2.start();
}
public static void method1() {
synchronized(String.class) {
try {
Thread.sleep(2000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1尝试获取Integer.class");
synchronized(Integer.class) {
System.out.println("线程1已经获取Integer.class");
}
}
}
public static void method2() {
//不再获取线程1需要的Integer.class锁
synchronized(String.class) {
try {
Thread.sleep(2000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2尝试获取Integer.class");
synchronized(Integer.class) {
System.out.println("线程2已经获取Integer.class");
}
}
}
}
运行结果:
在上面的例子中,已经不存在线程1 持有 线程2需要的锁,而线程2持有线程1需要的锁的逻辑了,所以demo顺利执行完毕。
小结:
产生死锁的情况:
1. 有多把锁,被不同线程或进程所持有,就会导致相互等待对方释放,程序就会卡死
2. 对同一把互斥锁加锁了多次
对于第一种情况的解决方案:
1. 抢占一定按照顺序抢
2. 给锁加上超时,如果超时则放弃执行
避免死锁的方式
1、让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实
2、设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量
3、既然死锁的产生是两个线程无限等待对方持有的锁,那么只要等待时间有个上限不就好了。当然synchronized不具备这个功能,但是我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后变回返回一个失败信息
在面试中能否阐述清楚死锁产生的原因及解决方案,能体现一个程序员对并发的掌握,解决问题的思路等等。在实际项目中并发模块的逻辑比示例复杂的多,要结合具体情况而定。
最后欢迎各位留言呦,学无止境,大家共勉之。。。