Java并发编程--死锁及模拟银行转账实现

死锁DeadLock

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

 

模拟银行转账的Demo

下面我们来看一个关于银行转账的死锁例子。在银行转账问题中,我们需要对转出方和转入方两个账户加锁,以确保转账过程中金额的正确性。

我们在一般实现中,可能会采用以下手段,比如类似先锁转出账户,后锁转入账户的方式。实际在很多类似订单操作、库存操作中可能都会有类似逻辑。

/**
 * 银行转账动作接口
 */
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));
        }
    }
}

 

你可能感兴趣的:(Java笔记,Java,多线程,并发)