1、简述
死锁(deadlock) 是指两个或多个线程因争夺资源而相互等待,导致所有线程都无法继续执行的一种状态。
死锁的四个必要条件:
- 互斥条件:某资源一次只能被一个线程占用。
- 占有且等待:一个线程已持有资源并等待其他线程的资源。
- 不可剥夺:资源不能被强行从线程中剥夺。
- 循环等待:多个线程形成一种头尾相接的等待资源关系链。
只要这四个条件同时满足,就可能产生死锁。
2、死锁示例代码
下面是一个典型的死锁示例:
public class deadlockexample {
private static final object locka = new object();
private static final object lockb = new object();
public static void main(string[] args) {
thread t1 = new thread(() -> {
synchronized (locka) {
system.out.println("thread-1: locked a");
try { thread.sleep(100); } catch (interruptedexception e) {}
synchronized (lockb) {
system.out.println("thread-1: locked b");
}
}
});
thread t2 = new thread(() -> {
synchronized (lockb) {
system.out.println("thread-2: locked b");
try { thread.sleep(100); } catch (interruptedexception e) {}
synchronized (locka) {
system.out.println("thread-2: locked a");
}
}
});
t1.start();
t2.start();
}
}
运行这段程序可能会导致 thread-1 等待 lockb,thread-2 等待 locka,从而产生死锁。
3、如何检测死锁?
3.1 使用 jstack
当应用卡住时,使用如下命令查看线程堆栈:
jps # 查找 java 进程 id jstack <pid>
你会看到类似:
found one java-level deadlock: "thread-1": waiting to lock monitor 0x000..., which is held by "thread-2" ...
3.2 使用 visualvm 或 jconsole
这类工具可以图形化展示线程状态,识别死锁非常直观。
4、如何预防和解决死锁?
4.1 统一资源获取顺序(推荐)
确保多个线程获取多个锁时 按相同顺序加锁。
public void safemethod() {
synchronized (locka) {
synchronized (lockb) {
// 安全操作
}
}
}
4.2 使用 trylock() 避免无限等待
使用 reentrantlock 的 trylock() 方法设置获取锁的超时时间。
import java.util.concurrent.locks.reentrantlock;
import java.util.concurrent.timeunit;
public class trylockexample {
private final reentrantlock locka = new reentrantlock();
private final reentrantlock lockb = new reentrantlock();
public void run() {
thread t1 = new thread(() -> {
try {
if (locka.trylock(1, timeunit.seconds)) {
system.out.println("thread-1: locked a");
thread.sleep(100);
if (lockb.trylock(1, timeunit.seconds)) {
system.out.println("thread-1: locked b");
lockb.unlock();
}
locka.unlock();
}
} catch (interruptedexception e) {
e.printstacktrace();
}
});
thread t2 = new thread(() -> {
try {
if (lockb.trylock(1, timeunit.seconds)) {
system.out.println("thread-2: locked b");
thread.sleep(100);
if (locka.trylock(1, timeunit.seconds)) {
system.out.println("thread-2: locked a");
locka.unlock();
}
lockb.unlock();
}
} catch (interruptedexception e) {
e.printstacktrace();
}
});
t1.start();
t2.start();
}
public static void main(string[] args) {
new trylockexample().run();
}
}
4.3 使用 java.util.concurrent 包
避免使用低级的 synchronized,使用高级并发工具如 executorservice、semaphore、lock 等更可控的工具。
4.4 死锁检测与恢复
一些大型系统中,可以通过定期扫描线程状态,自动检测死锁并重启部分线程或服务。例如通过自定义 threadmxbean 检测死锁。
最佳实践小结:
| 技术手段 | 说明 |
|---|---|
| 加锁顺序统一 | 所有线程按相同顺序获取资源 |
| trylock + timeout | 尝试加锁失败后避免长时间阻塞 |
| lock 分解 | 将大锁拆分成小锁减少竞争 |
| 并发工具替代 synchronized | 使用 reentrantlock、semaphore 等 |
| 死锁检测工具 | 使用 jstack、visualvm 等工具 |
5、结语
死锁是多线程编程中不可忽视的问题,但并不是无法避免。只要我们在设计时保持锁的有序性,并结合现代并发工具进行控制,绝大多数死锁问题都是可以预防的。
以上就是java死锁问题解决方案及示例详解的详细内容,更多关于java死锁问题解决的资料请关注代码网其它相关文章!
发表评论