死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
下面我们来看一个关于银行转账的死锁例子。在银行转账问题中,我们需要对转出方和转入方两个账户加锁,以确保转账过程中金额的正确性。
我们在一般实现中,可能会采用以下手段,比如类似先锁转出账户,后锁转入账户的方式。实际在很多类似订单操作、库存操作中可能都会有类似逻辑。
/**
* 银行转账动作接口
*/
public interface ITransfer {
/**
*
* @param from 转出账户
* @param to 转入账户
* @param amount 转账金额
* @throws InterruptedException
*/
void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException;
}
/**
* 不安全的转账动作
*/
public class TrasnferAccount implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
synchronized (from){//先锁转出
System.out.println(Thread.currentThread().getName()
+" get"+from.getName());
Thread.sleep(100);
synchronized (to){//再锁转入
System.out.println(Thread.currentThread().getName()
+" get"+to.getName());
// TODO 业务代码
}
}
}
}
而实际上,这个是不安全的操作。比如我们同时对A->B和B->A做转账操作,就可能产生死锁现象。
ITransfer transfer = new TrasnferAccount();
TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi"
,zhangsan,lisi,2000,transfer);
TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan"
,lisi,zhangsan,2000,transfer);
方案一:可以通过顺序加锁的方式,比如类似id或hash值排序,以下为通过hash实现的例子
/**
* 不会产生死锁的安全转账
*/
public class SafeOperate implements ITransfer {
private static Object tieLock = new Object();//加时赛锁
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
//先锁hash小的那个
if(fromHash
方案二:可以通过尝试加锁的形式,而非直接加锁
在UserAccount里增加私有变量显示锁,通过tryLock尝试获取
//显示锁
private final Lock lock = new ReentrantLock();
public Lock getLock() {
return lock;
}
/**
* 不会产生死锁的安全转账第二种方法,尝试拿锁
*/
public class SafeOperate implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
Random r = new Random();
while(true) {
if(from.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+" get "+from.getName());
if(to.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+" get "+to.getName());
//两把锁都拿到了
// TODO 业务代码
break;
}finally {
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
Thread.sleep(r.nextInt(10));
}
}
}