Java并发编程之死锁相关知识整理
什么是死锁?
死锁是指两个或多个线程在执行过程中,因互相竞争资源而造成的一种互相等待的现象,若无外力干涉势将无法推进下去。
什么情况下会发生死锁?
当系统资源不足时,进程会因争夺资源而陷入僵局。若此时系统能够协调资源分配,以便令进程有序地进行,便可避免进程间死锁的发生。
在Java并发编程中,一般出现死锁的情况是因为线程之间相互等待资源而产生的。例如,当线程A占有资源a并请求资源b,但此时资源b已经被线程B占有并在请求资源a,线程A和线程B都无法执行下去,就陷入了死锁。
如何避免死锁?
避免死锁的方法有以下几种:
- 避免一个线程同时获取多个锁:此时就要考虑是否可以减少锁的使用或者更换锁的实现方式,如使用ReentrantLock代替synchronized;
- 避免线程之间相互等待对方释放锁:此时可以考虑使用tryLock,并设置等待超时时间,或者使用LockSupport.park()和LockSupport.unpark()来防止线程被永久阻塞;
- 避免嵌套锁:锁的嵌套使用会增加线程产生死锁的概率,需要尽量避免;
- 按照顺序获取锁:对于多个资源,线程按照预定顺序加锁,从而避免出现循环等待的情况。
示例
下面我们演示一个简单的死锁示例,代码如下:
public class DeadlockDemo {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(() -> {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "获取lock1成功");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "获取lock2成功");
}
}
}, "线程A").start();
new Thread(() -> {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "获取lock2成功");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "获取lock1成功");
}
}
}, "线程B").start();
}
}
在上面的示例中,线程A和线程B都需要获取lock1和lock2两个锁才能完成操作,如果此时线程A获取了lock1,而线程B获取了lock2,然后两个线程都等待对方释放锁,就会陷入死锁的状态。
下面是一个使用LockSupport等待超时的示例:
public class DeadlockDemo2 {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "尝试获取lock1");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "获取lock1成功");
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
System.out.println(Thread.currentThread().getName() + "尝试获取lock2");
if (lock2 instanceof Lock) {
((Lock) lock2).tryLock(1, TimeUnit.SECONDS);
} else {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "获取lock2成功");
((Lock) lock1).unlock();
((Lock) lock2).unlock();
}
}
}
}, "线程A").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "尝试获取lock2");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "获取lock2成功");
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
System.out.println(Thread.currentThread().getName() + "尝试获取lock1");
if (lock1 instanceof Lock) {
((Lock) lock1).tryLock(1, TimeUnit.SECONDS);
} else {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "获取lock1成功");
((Lock) lock1).unlock();
((Lock) lock2).unlock();
}
}
}
}, "线程B").start();
}
}
在上面的示例中,如果线程A获取了lock1而没能获取锁2,那么在等待1秒后会尝试获取锁2,但是使用LockSupport.parkNanos和LockSupport.unpark可以实现线程等待超时,这样可以避免线程永久阻塞。如果在1秒内没能获取到锁2,就会继续执行并释放锁1,从而避免了死锁。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java并发编程之死锁相关知识整理 - Python技术站