死锁是指两个或者多个线程在执行过程中,因争夺资源而造成的一种相互等待的现象。也就是说每个线程已经获取了其中一个对象上的锁,而且正在等待另一个对象上的错。考虑有两个线程和两个对象的情形,如下图所示。线程1获取object1上的锁,线程2获取object2上的锁,现在线程1等待object2上的锁,线程2等待object1上的锁。每个线程都在等待另一个线程释放它所需要的锁,导致两个线程都无法继续运行(死锁)。
死锁产生的四个必要条件
互斥条件:资源一次只能由一个线程占用
请求与保持条件:线程持有至少一个资源,并等待获取其它线程持有的资源
不剥夺条件:线程已获得的资源在未使用完之前不能被其它线程强行剥夺
循环等待条件:多个线程形成一种头尾相接的循环等待资源关系
示例:账户转账情形
package edu.cuit.avatar.concurrent;
/**
* @author Adan
* @version 1.0
* @date 2025/7/17 10:42
*/
public class BankDeadLockDemo {
private static class Account{
private String name;
private int balance;
public Account(String name, int balance) {
this.name = name;
this.balance = balance;
}
public int getBalance() {
return balance;
}
public String getName() {
return name;
}
public void deposit(int amount){
balance += amount;
}
public void withdraw(int amount){
balance -= amount;
}
@Override
public String toString() {
return name + ':' + balance;
}
}
public static void transfer(Account from, Account to, int amount){
synchronized (from){
System.out.println(Thread.currentThread().getName() + " acquired lock on " + from);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (to){
System.out.println(Thread.currentThread().getName() + " acquired lock on " + to);
if(from.balance >= amount){
from.withdraw(amount);
to.deposit(amount);
System.out.println("transfer successful!");
} else
System.out.println("insufficient balance");
}
}
}
public static void main(String[] args) {
Account peppa = new Account("Peppa", 1000);
Account jorge = new Account("jorge", 1000);
new Thread(()-> transfer(peppa, jorge, 500)).start();
new Thread(()-> transfer(jorge, peppa, 500)).start();
}
}
如何避免死锁
使用一种称为资源排序的简单技术可以轻易地避免死锁的发生。该技术是给每一个需要锁的对象指定一个顺序,确保每个线程都按照这个顺序来获取锁。例如,在上图中,假设按照object1,object2的顺序对两个对象进行排序。采用资源排序技术,线程2必须先获取object1上的锁,然后才能获取object2上的锁。一旦线程1获取了object1上的锁,线程2必须等待object1上的锁。所以,线程1就能获取object2上的锁,不会再发生死锁的现象。
使用资源排序技术解决上述转账问题
package edu.cuit.avatar.concurrent;
/**
* @author Adan
* @version 1.0
* @date 2025/7/17 10:42
*/
public class BankDeadLockDemo {
private static class Account{
private String name;
private int balance;
public Account(String name, int balance) {
this.name = name;
this.balance = balance;
}
public int getBalance() {
return balance;
}
public String getName() {
return name;
}
public void deposit(int amount){
balance += amount;
}
public void withdraw(int amount){
balance -= amount;
}
@Override
public String toString() {
return name + ':' + balance;
}
}
public static void transfer(Account from, Account to, int amount){ //资源排序:确保所有线程以相同顺序获取锁
Account object1 = from.name.compareTo(to.name) < 0 ? from : to;
Account object2 = object1 == from ? to : from;
synchronized (object1){
System.out.println(Thread.currentThread().getName() + " acquired lock on " + from);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object2){
System.out.println(Thread.currentThread().getName() + " acquired lock on " + to);
if(from.balance >= amount){
from.withdraw(amount);
to.deposit(amount);
System.out.println("transfer successful!");
} else
System.out.println("insufficient balance");
}
}
}
public static void main(String[] args) {
Account peppa = new Account("Peppa", 1000);
Account jorge = new Account("jorge", 1000);
new Thread(()-> transfer(peppa, jorge, 500)).start();
new Thread(()-> transfer(jorge, peppa, 500)).start();
}
}
检测死锁
可以使用JDK工具检测死锁:
运行程序
使用jps命令查找java进程PID
使用jstack