死锁
在Java中,线程死锁是指两个或多个线程无限期地被阻塞,等待对方所持有的资源,导致所有线程都无法继续执行。这种情况下,程序会陷入死循环,无法正常结束。线程死锁是多线程编程中一种非常棘手的问题,因此在编写多线程程序时,需要特别注意避免死锁的发生。
相关概念
- 互斥:多个线程访问同一资源时,任何时刻只允许一个线程进行访问,其他线程必须等待该线程释放该资源后才能访问。
- 不可剥夺性:已经被一个线程持有的资源,在没有得到该线程释放的情况下,其他线程不能将其抢占或者剥夺。
- 请求和保持:一个线程在持有某个资源的同时,又请求其他资源。
- 循环等待:一组线程互相等待彼此持有的资源,形成一个循环等待的环路。
当满足以上四个条件时,就可能导致线程死锁的发生。
因此,在编写多线程程序时,我们需要注意以下几点,以避免线程死锁的发生:
- 避免单个线程持有多个锁。如果需要多个锁,请确保线程获取锁的顺序始终是一致的。这样可以避免不同线程获取锁的顺序不同,导致死锁的发生。
- 尽量缩小锁的作用范围。当需要多个线程进行资源竞争时,可以将锁的作用范围缩小到必要的最小范围内,从而减少线程等待锁的时间和减少死锁的发生概率。
- 使用tryLock()方法代替lock()方法。当线程在等待锁的时候,可以使用tryLock()方法尝试获取锁,而不是一直等待。如果获取锁成功,则可以继续执行,否则可以释放已经持有的锁,避免死锁的发生。
- 尽量避免线程之间的循环依赖。如果多个线程之间存在循环依赖关系,那么就很容易导致死锁的发生。因此,在设计多线程程序时,应该尽量避免线程之间的循环依赖关系。
- 使用定时锁。在获取锁的时候可以使用定时锁,如果在一定时间内无法获取到锁,则放弃获取,避免一直等待导致死锁的发生。
代码示例
代码示例,演示了线程死锁的情况:
public class DeadlockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void executeThread1() {
synchronized (lock1) {
System.out.println("Thread 1 acquired lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Thread 1 acquired lock2");
}
}
}
public void executeThread2() {
synchronized (lock2) {
System.out.println("Thread 2 acquired lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Thread 2 acquired lock1");
}
}
}
public static void main(String[] args) {
DeadlockExample example = new DeadlockExample();
Thread thread1 = new Thread(() -> example.executeThread1());
Thread thread2 = new Thread(() -> example.executeThread2());
thread1.start();
thread2.start();
}
}
在上述代码中,有两个线程分别执行 executeThread1() 和 executeThread2() 方法。这两个方法都会尝试获取两个锁 lock1 和 lock2,但是获取锁的顺序不同。
假设线程1先获取了 lock1 锁,然后线程2获取了 lock2 锁。当线程1尝试获取 lock2 锁时,会因为该锁被线程2占用而进入等待状态,同时线程2尝试获取 lock1 锁时也会因为该锁被线程1占用而进入等待状态。这种情况下,两个线程会相互等待对方释放锁,导致死锁的产生
释放锁
在Java中,当一个线程已经获取了一个锁,但是在持有锁的情况下,由于某些原因无法完成后续操作时,为了避免死锁,需要及时释放该锁。
- finally块中释放锁:finally块中的代码无论前面的try块中是否出现异常都会被执行,因此可以在finally块中释放锁。
- synchronized关键字的自动释放锁:当一个线程获取到了某个对象的锁时,其他线程无法访问该对象的同步方法或同步块,当该线程执行完同步方法或同步块后,该锁会自动释放。4t
public class LockExample {
private final Object lock = new Object();
public void doSomething() {
synchronized (lock) {
try {
// do something
} finally {
// 释放锁
lock.notifyAll();
}
}
}
}
上述示例中,通过synchronized关键字获取了一个锁,使用try-finally语句块确保锁的释放。在finally块中,调用notifyAll()方法释放锁,该方法会唤醒在此锁上等待的所有线程。