Java死锁和解决方法(1)
1、简介
在遇到线程安全问题的时候,我们会使用加锁机制来确保线程安全,但如果过度地使用加锁,则可能导致锁顺序死锁(Lock-Ordering Deadlock)。或者有的场景我们使用线程池和信号量来限制资源的使用,但这些被限制的行为可能会导致资源死锁(Resource DeadLock)。这是来自Java并发必读佳作 Java Concurrency in Practice 关于活跃性危险中的描述。 我们知道Java应用程序不像数据库服务器,能够检测一组事务中死锁的发生,进而选择一个事务去执行;在Java程序中如果遇到死锁将会是一个非常严重的问题,它轻则导致程序响应时间变长,系统吞吐量变小;重则导致应用中的某一个功能直接失去响应能力无法提供服务,这些后果都是不堪设想的。因此我们应该及时发现和规避这些问题。
2、死锁产生的条件
死锁的产生有四个必要的条件:
1.互斥使用,即当资源被一个线程占用时,别的线程不能使用。
2.不可抢占,资源请求者不能强制从资源占有者手中抢夺资源,资源只能由占有者主动释放。
3.请求和保持,当资源请求者在请求其他资源的同时保持对原有资源的占有。
4.循环等待,多个线程存在环路的锁依赖关系而永远等待下去,例如T1占有T2的资源,T2占有T3的资源,T3占有T1的资源,这种情况可能会形成一个等待环路。
对于死锁产生的四个条件只要能破坏其中一条即可让死锁消失,但是条件一是基础,不能被破坏。
3、各种死锁的介绍
3.1 锁顺序死锁
如图描述:
8955b0f38d494295fb89a371b974ffd.png
3.1.1 采用synchronized方式演示死锁:
//死锁测试和解决方案(采用synchronized代码块)
public class DeadLockTest {
private static final String TAG = DeadLockTest.class.getName();
private Object lock1 = new Object();
private Object lock2 = new Object();
public class ThreadA extends Thread {
@Override
public void run() {
super.run();
Log.i(TAG, "线程A尝试获取lock1");
synchronized (lock1) {
Log.i(TAG, "线程A获得了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "线程A休眠后尝试获取lock2");
synchronized (lock2) {
Log.i(TAG, "线程A获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Log.i(TAG, "线程A执行完毕");
}
}
public class ThreadB extends Thread {
@Override
public void run() {
super.run();
Log.i(TAG, "线程B尝试获取lock2");
synchronized (lock2) {
Log.i(TAG, "线程B获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "线程B休眠后尝试获取lock1");
synchronized (lock1) {
Log.i(TAG, "线程B获得了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Log.i(TAG, "线程B执行完毕");
}
}
//测试方法:
public void testDeadLock01() {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
}
}
调用testDeadLock01方法,看下运行结果:
线程A尝试获取lock1
线程A获得了lock1
线程B尝试获取lock2
线程B获得了lock2
线程A休眠后尝试获取lock2
线程B休眠后尝试获取lock1
可以看到:
1.线程A获取lock1后没有获取到lock2(此时lock2被线程B获取并未释放),获取锁顺序为:lock1->lock2
2.线程B获取lock2后没有获取到lock1(此时lock1被线程A获取并未释放),获取锁顺序为:lock2->lock1
3.线程A和线程B相互等待,就造成了死锁
解决方案:
解决顺序死锁的办法其实就是保证所有线程以相同的顺序获取锁就行。
改进如下(测试代码改了线程名字,用线程C和D演示,获取锁的顺序均为lock1->lock2)
public class DeadLockTest {
private static final String TAG = DeadLockTest.class.getName();
private Object lock1 = new Object();
private Object lock2 = new Object();
public class ThreadC extends Thread {
@Override
public void run() {
super.run();
Log.i(TAG, "线程C尝试获取lock1");
synchronized (lock1) {
Log.i(TAG, "线程C获得了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "线程C休眠后尝试获取lock2");
synchronized (lock2) {
Log.i(TAG, "线程C获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Log.i(TAG, "线程C执行完毕");
}
}
public class ThreadD extends Thread {
@Override
public void run() {
super.run();
Log.i(TAG, "线程D尝试获取lock1");
synchronized (lock1) {
Log.i(TAG, "线程D获得了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "线程D休眠后尝试获取lock2");
synchronized (lock2) {
Log.i(TAG, "线程D获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Log.i(TAG, "线程D执行完毕");
}
}
/**
* 线程C和线程D获取lock1和lock2的顺序是相同的
*/
public void testDeadLock02() {
ThreadC threadC = new ThreadC();
ThreadD threadD = new ThreadD();
threadC.start();
threadD.start();
}
}
调用testDeadLock02方法,运行结果:
线程C尝试获取lock1
线程C获得了lock1
线程D尝试获取lock1
线程C休眠后尝试获取lock2
线程C获得了lock2
线程C执行完毕
线程D获得了lock1
线程D休眠后尝试获取lock2
线程D获得了lock2
线程D执行完毕
可以看到:
1.线程C获取lock1后,接着获取lock2,此时线程D在等待线程C释放lock1,
2.等线程C执行完synchronized(lock1)代码块也就释放了lock1和lock2锁,
3.之后线程D就拿到了lock1和lock2锁,这样就不会发生死锁。
3.1.2 采用Lock方式演示死锁(原理同synchronized)
public class DeadLockTest2 {
private static final String TAG = DeadLockTest2.class.getName();
private ReentrantLock lock1 = new ReentrantLock();
private ReentrantLock lock2 = new ReentrantLock();
public class ThreadA extends Thread {
@Override
public void run() {
super.run();
try {
Log.i(TAG, "线程A尝试获取lock1");
lock1.lock();
Log.i(TAG, "线程A获得了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Log.i(TAG, "线程A休眠后尝试获取lock2");
lock2.lock();
Log.i(TAG, "线程A获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程A释放lock2");
lock2.unlock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程A释放lock1");
lock1.unlock();
}
Log.i(TAG, "线程A执行完毕");
}
}
public class ThreadB extends Thread {
@Override
public void run() {
super.run();
try {
Log.i(TAG, "线程B尝试获取lock2");
lock2.lock();
Log.i(TAG, "线程B获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Log.i(TAG, "线程B休眠后尝试获取lock1");
lock1.lock();
Log.i(TAG, "线程B获得了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程B释放lock1");
lock1.unlock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程B释放lock2");
lock2.unlock();
}
Log.i(TAG, "线程B执行完毕");
}
}
public void testDeadLock01() {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
}
}
调用testDeadLock01方法,运行结果:
线程A尝试获取lock1
线程A获得了lock1
线程B尝试获取lock2
线程B获得了lock2
线程A休眠后尝试获取lock2
线程B休眠后尝试获取lock1
结果为死锁。
解决方法:
public class DeadLockTest2 {
private static final String TAG = DeadLockTest2.class.getName();
private ReentrantLock lock1 = new ReentrantLock();
private ReentrantLock lock2 = new ReentrantLock();
public class ThreadC extends Thread {
@Override
public void run() {
super.run();
try {
Log.i(TAG, "线程C尝试获取lock1");
lock1.lock();
Log.i(TAG, "线程C获得了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Log.i(TAG, "线程C休眠后尝试获取lock2");
lock2.lock();
Log.i(TAG, "线程C获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程C释放lock2");
lock2.unlock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程C释放lock1");
lock1.unlock();
}
Log.i(TAG, "线程C执行完毕");
}
}
public class ThreadD extends Thread {
@Override
public void run() {
super.run();
try {
Log.i(TAG, "线程D尝试获取lock1");
lock1.lock();
Log.i(TAG, "线程D获得了lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Log.i(TAG, "线程D休眠后尝试获取lock2");
lock2.lock();
Log.i(TAG, "线程D获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程D释放lock2");
lock2.unlock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程D释放lock1");
lock1.unlock();
}
Log.i(TAG, "线程D执行完毕");
}
}
public void testDeadLock02() {
ThreadC threadC = new ThreadC();
ThreadD threadD = new ThreadD();
threadC.start();
threadD.start();
}
}
调用testDeadLock02方法,运行结果:
线程C尝试获取lock1
线程C获得了lock1
线程D尝试获取lock1
线程C休眠后尝试获取lock2
线程C获得了lock2
线程C释放lock2
线程C释放lock1
线程C执行完毕
线程D获得了lock1
线程D休眠后尝试获取lock2
线程D获得了lock2
线程D释放lock2
线程D释放lock1
可见解决了死锁问题。
3.1.3 采用Lock方式演示死锁解决方案(原理同synchronized)
以下也是Lock方式,跟3.1.2效果一样,只不过用了tryLock方法演示(仅供参考)
public class DeadLockTest2 {
private static final String TAG = DeadLockTest2.class.getName();
private ReentrantLock lock1 = new ReentrantLock();
private ReentrantLock lock2 = new ReentrantLock();
//tryLock的写法
public class ThreadE extends Thread {
@Override
public void run() {
super.run();
Log.i(TAG, "线程E尝试获取lock1");
while(true) {
if (lock1.tryLock()) {
try {
Log.i(TAG, "线程E获得了lock1");
for (int i=0; i <3;i++) {
Log.i(TAG, "线程E========>执行任务i="+i);
}
Log.i(TAG, "线程E休眠后尝试获取lock2");
if (lock2.tryLock()) {
try {
Log.i(TAG, "线程E获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程E释放lock2");
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
}
}
} catch(Exception e) {
e.printStackTrace();
} finally{
if (lock1.isHeldByCurrentThread()) {
Log.i(TAG, "线程E释放lock1");
lock1.unlock();
}
Log.i(TAG, "线程E执行完毕");
//跳出while循环
break;
}
}
}
Log.i(TAG, "线程E跳出while循环");
}
}
public class ThreadF extends Thread {
@Override
public void run() {
super.run();
while (true) {
//每隔1获取一次锁,直到获取到为止
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "线程F尝试获取lock1");
if (lock1.tryLock()) {
try {
Log.i(TAG, "线程F获得了lock1");
for (int i=0; i <3;i++) {
Log.i(TAG, "线程F----->执行任务i="+i);
}
Log.i(TAG, "线程F休眠后尝试获取lock2");
if (lock2.tryLock()) {
try {
Log.i(TAG, "线程F获得了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "线程F释放lock2");
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
}
}
} catch(Exception e) {
e.printStackTrace();
} finally{
if (lock1.isHeldByCurrentThread()) {
Log.i(TAG, "线程F释放lock1");
lock1.unlock();
}
Log.i(TAG, "线程F执行完毕");
break;
}
}
}
Log.i(TAG, "线程F跳出while循环");
}
}
public void testDeadLock03() {
ThreadE threadE = new ThreadE();
ThreadF threadF = new ThreadF();
threadE.start();
threadF.start();
}
}
调用testDeadLock03方法,执行结果:
线程E尝试获取lock1
线程E获得了lock1
线程E========>执行任务i=0
线程E========>执行任务i=1
线程E========>执行任务i=2
线程E休眠后尝试获取lock2
线程E获得了lock2
线程F尝试获取lock1
线程E释放lock2
线程E释放lock1
线程E执行完毕
线程E跳出while循环
线程F尝试获取lock1
线程F获得了lock1
线程F----->执行任务i=0
线程F----->执行任务i=1
线程F----->执行任务i=2
线程F休眠后尝试获取lock2
线程F获得了lock2
线程F释放lock2
线程F释放lock1
线程F执行完毕
线程F跳出while循环
结果解决了死锁问题。