定义
- 活锁:一种并发编程中的现象,其中两个或多个线程相互等待对方释放资源,但由于某种原因,没有任何线程能够继续执行,从而导致所有涉及的线程都陷入无限循环。
- 死锁:一种并发编程中的现象,其中两个或多个线程无限期地等待对方释放锁定的资源,导致所有涉及的线程都无法继续执行。
区别
活锁和死锁虽然都是并发编程中的问题,但两者之间存在一些关键区别:
- 资源获取:在死锁中,线程已经获取了所需的资源并被锁住,而活锁中线程尚未获取资源。
- 线程状态:在死锁中,线程处于 BLOCKED 状态,等待其他线程释放锁定的资源。在活锁中,线程处于 RUNNABLE 状态,但由于某种原因无法继续执行。
- 检测方法:死锁可以通过检测循环等待的线程来检测,而活锁更难检测,因为它涉及线程之间的间接依赖关系。
活锁的示例
考虑以下代码示例:
public class LiveLockExample {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
// ...
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
// ...
}
}
});
thread1.start();
thread2.start();
}
}
在这个示例中,两个线程相互等待对方释放锁定的对象,导致活锁。线程 1 等待线程 2 释放 lock2
,而线程 2 等待线程 1 释放 lock1
。由于两个线程都在不断尝试获取对方持有的锁,它们陷入了一个无限循环,无法继续执行。
死锁的示例
考虑以下代码示例:
public class DeadlockExample {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
// ...
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
// ...
}
}
});
thread1.start();
thread2.start();
// 等待线程完成
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个示例中,两个线程都试图获取两个锁 (lock1
和 lock2
),但由于线程 1 先获取了 lock1
,而线程 2 先获取了 lock2
,它们都无法继续执行并等待对方释放锁。这导致了死锁。
如何避免
避免活锁和死锁的方法包括:
- 避免嵌套锁
- 始终以相同的顺序获取锁
- 使用
Lock
对象和tryLock()
方法 - 使用非阻塞数据结构
- 避免资源饥饿
结论
活锁和死锁都是并发编程中常见的陷阱。理解它们的差异并采取适当的预防措施对于编写健壮且无错误的多线程应用程序至关重要。