大部分死锁都是由于不恰当的加锁顺序造成的,例如,线程A持有lock1等待lock2,而线程B持有lock2等待lock1,解决办法是每个线程都以相同的顺序获取多个锁。
先看一个由于错误的加锁顺序导致的死锁:
public class TransferMoneyDeadLock {
public void transferMoney(Account fromAccount, Account toAccount, BigDecimal amount) throws InterruptedException {
synchronized (fromAccount){
Thread.sleep(2000);
synchronized (toAccount) {
if(fromAccount.getAmount().compareTo(amount) < 0) {
throw new RuntimeException("转账金额错误");
} else {
fromAccount.debit(amount);
toAccount.credit(amount);
System.out.println("fromAccount amount is " + fromAccount.getAmount() + ", toAccount amount is " + toAccount.getAmount());
}
}
}
}
public static void main(String[] agrs) {
TransferMoneyDeadLock transferMoneyDeadLock = new TransferMoneyDeadLock();
Account fromAccount = new Account(1231231233, new BigDecimal("10000"));
Account toAccount = new Account(1231231231, new BigDecimal("10000"));
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(
new Runnable() {
@Override
public void run() {
try {
transferMoneyDeadLock.transferMoney(fromAccount, toAccount, new BigDecimal("2000"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
);
executorService.submit(
new Runnable() {
@Override
public void run() {
try {
transferMoneyDeadLock.transferMoney(toAccount ,fromAccount, new BigDecimal("2000"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
);
}
}
public class Account {
private long userId;
private BigDecimal amount;
public Account(long userId, BigDecimal amount) {
this.userId = userId;
this.amount = amount;
}
public long getUserId() {
return userId;
}
public void debit(BigDecimal amount) {
this.amount = this.amount.subtract(amount);
}
public void credit(BigDecimal amount) {
this.amount = this.amount.add(amount);
}
public void setUserId(long userId) {
this.userId = userId;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
}
可以利用类似userId这种唯一且可以比较大小的字段来给多线程设置一个固定的加锁顺序,所有的线程都按这个顺序获取锁,就不会出现死锁的问题。
public class TransferMoneySafely {
private final Object lock = new Object();
public void transferMoney(Account fromAccount, Account toAccount, BigDecimal amount) throws InterruptedException {
if(fromAccount.getUserId() > toAccount.getUserId()) {
synchronized (fromAccount) {
Thread.sleep(2000);
synchronized (toAccount) {
doTransfer(fromAccount,toAccount, amount);
}
}
} else if(fromAccount.getUserId() < toAccount.getUserId()) {
synchronized (toAccount) {
Thread.sleep(2000);
synchronized (fromAccount) {
doTransfer(fromAccount,toAccount, amount);
}
}
} else {
synchronized (lock) {
synchronized (fromAccount){
Thread.sleep(2000);
synchronized (toAccount) {
doTransfer(fromAccount,toAccount, amount);
}
}
}
}
}
public void doTransfer(Account fromAccount, Account toAccount, BigDecimal amount) {
if(fromAccount.getAmount().compareTo(amount) < 0) {
throw new RuntimeException("转账金额错误");
} else {
fromAccount.debit(amount);
toAccount.credit(amount);
System.out.println("fromAccount amount is " + fromAccount.getAmount() + ", toAccount amount is " + toAccount.getAmount());
}
}
public static void main(String[] agrs) {
TransferMoneySafely transferMoneySafely = new TransferMoneySafely();
Account fromAccount = new Account(1231231233, new BigDecimal("10000"));
Account toAccount = new Account(1231231231, new BigDecimal("10000"));
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(
new Runnable() {
@Override
public void run() {
try {
transferMoneySafely.transferMoney(fromAccount, toAccount, new BigDecimal("2000"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
);
executorService.submit(
new Runnable() {
@Override
public void run() {
try {
transferMoneySafely.transferMoney(toAccount ,fromAccount, new BigDecimal("2000"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
);
}
}
或者,使用Lock
public boolean transferMoney(Account fromAccount, Account toAccount, BigDecimal amount, long timeout, TimeUnit unit) throws InsufficientResourcesException, InterruptedException { //回避时间 = 固定时间+随机时间 long fixDelay = 2; Random random = new Random(); random.setSeed(fixDelay); long randMod = random.nextLong(); long stopTime = System.nanoTime() + unit.toNanos(timeout); while(true) { if(fromAccount.lock.tryLock()) { try { if(toAccount.lock.tryLock()) { try { if(fromAccount.getAmount().compareTo(amount) < 0) { throw new InsufficientResourcesException(); } else { fromAccount.debit(amount); toAccount.credit(amount); System.out.println("fromAccount amount is " + fromAccount.getAmount() + ", toAccount amount is " + toAccount.getAmount()); return true; } } finally { //释放锁必须放在finally块中,否则,异常时无法释放锁 toAccount.lock.unlock(); } } }finally { fromAccount.lock.unlock(); } } //超时退出 if(System.nanoTime() > stopTime) { System.out.println("Time out!"); return false; } //回退 NANOSECONDS.sleep(fixDelay + random.nextLong() % randMod); } }