在Java中,死锁是一种多线程并发执行的情况,其中两个或多个线程相互等待对方释放持有的资源,导致所有线程无法继续执行。这种情况下,系统处于僵持状态,被称为死锁。
死锁产生的条件通常是由于每个线程都在等待另一个线程释放资源,而这些资源又是其他线程正在等待的。死锁的发生通常涉及到多个锁、多个线程以及竞争共享资源。
互斥条件(Mutual Exclusion)
至少有一个资源必须处于非共享模式,即一次只能被一个进程使用。如果一个进程占用了资源,其他进程必须等待释放。
示例代码:
public class MutualExclusionExample {
private static class Resource {
}
private static Resource resourceA = new Resource();
private static Resource resourceB = new Resource();
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized (resourceA) {
System.out.println("Thread A: Holding resource A");
// 模拟一些工作
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceB) {
System.out.println("Thread A: Holding resource B");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (resourceB) {
System.out.println("Thread B: Holding resource B");
// 模拟一些工作
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA) {
System.out.println("Thread B: Holding resource A");
}
}
});
threadA.start();
threadB.start();
}
}
请求与保持条件(Hold and Wait)
进程已经保持至少一个资源,并且正在请求另一个资源,但由于其他进程持有了被请求的资源,因此该进程会被阻塞。进程在等待其他资源的同时,不释放已经持有的资源。
示例代码:
public class HoldAndWaitExample {
private static class HoldAndWaitResource {
synchronized void methodA(HoldAndWaitResource other) {
System.out.println(Thread.currentThread().getName() + " acquiring resource A");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
other.methodB(this);
System.out.println(Thread.currentThread().getName() + " releasing resource A");
}
synchronized void methodB(HoldAndWaitResource other) {
System.out.println(Thread.currentThread().getName() + " acquiring resource B");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
other.methodA(this);
System.out.println(Thread.currentThread().getName() + " releasing resource B");
}
}
public static void main(String[] args) {
HoldAndWaitResource resourceA = new HoldAndWaitResource();
HoldAndWaitResource resourceB = new HoldAndWaitResource();
Thread threadA = new Thread(() -> resourceA.methodA(resourceB), "Thread A");
Thread threadB = new Thread(() -> resourceB.methodB(resourceA), "Thread B");
threadA.start();
threadB.start();
}
}
非剥夺条件(No Preemption)
系统不能抢占进程已经占有的资源,只能在该进程自愿释放资源时,才能够重新分配资源给其他进程。
示例代码:
public class NoPreemptionExample {
private static class NoPreemptionResource {
private boolean locked = false;
synchronized void lock() {
while (locked) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
locked = true;
}
synchronized void unlock() {
locked = false;
notify();
}
}
public static void main(String[] args) {
NoPreemptionResource resource = new NoPreemptionResource();
Thread threadA = new Thread(() -> {
resource.lock();
System.out.println("Thread A: Holding the resource");
// 模拟一些工作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.unlock();
System.out.println("Thread A: Releasing the resource");
});
Thread threadB = new Thread(() -> {
resource.lock();
System.out.println("Thread B: Holding the resource");
// 模拟一些工作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.unlock();
System.out.println("Thread B: Releasing the resource");
});
threadA.start();
threadB.start();
}
}
循环等待条件(Circular Wait)
存在一个等待链,使得每个进程都占用着下一个进程所需的资源。形成一个环路,导致系统无法向前推进。
示例代码:
public class CircularWaitExample {
private static class CircularWaitResource {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public void methodA() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + " acquired lockA");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
methodB();
}
}
public void methodB() {
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + " acquired lockB");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
methodA();
}
}
}
public static void main(String[] args) {
CircularWaitResource resource = new CircularWaitResource();
Thread threadA = new Thread(() -> resource.methodA(), "Thread A");
Thread threadB = new Thread(() -> resource.methodB(), "Thread B");
threadA.start();
threadB.start();
}
避免死锁
按顺序获取锁: 规定所有线程按照相同的顺序获取锁。这样可以降低死锁的可能性,因为线程按照相同的顺序请求资源,不会形成循环等待条件。
使用定时锁: 在获取锁时设置超时时间,如果在规定时间内没有获取到锁,则放弃当前锁并释放已获得的锁。这有助于防止持有锁的线程在等待其他资源时导致死锁。
使用 tryLock(): 在Java中,
ReentrantLock
类提供了tryLock()
方法,该方法尝试获取锁,但不会阻塞线程。通过使用tryLock()
,可以避免线程在等待资源时阻塞。避免嵌套锁: 尽量避免在持有一个锁的时候去请求另一个锁。如果需要多个锁,可以尝试设计成获取一个锁的时候释放其他锁,以减少持有锁的时间。
使用统一的锁顺序: 如果多个线程需要获取多个锁,确保所有线程以相同的顺序获取这些锁。这样可以减少循环等待的可能性。
使用锁的层次结构: 将锁划分为层次结构,按照层级顺序获取锁。例如,按照资源的级别从低到高获取锁,而不是随意获取。
使用事务: 在数据库操作中,使用事务来确保一组操作的一致性,可以减少死锁的可能性。数据库管理系统通常具有处理并发事务的机制。
使用专门的工具和算法: 一些系统和工具提供死锁检测和解决的机制。使用这些工具可以及时发现死锁并采取适当的措施。