Java并发编程实战读书笔记之死锁
Posted 郭梧悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程实战读书笔记之死锁相关的知识,希望对你有一定的参考价值。
死锁的分类
前言
本篇学习笔记源自于《Java并发编程实战》第10章。
提示:以下是本篇文章正文内容,下面案例可供参考
一、锁顺序死锁
看下面代码,很容易造成死锁,leftRight和rightLeft方法分别获取了left和right锁,只不过二者获取锁的顺序是相反的。那么如果一个Thread调用了leftRight,另一个Thread调用了rightLeft,并且两个Thread的操作是交错执行的,那么就容易发生时所。
class LeftRightDeadlock
private final Object left = new Object();
private final Object right = new Object();
public void leftRight()
synchronized(left)
synchronized(right)
doSomethingA();
public void rightLeft()
synchronized(right)
synchronized(left)
doSomethingB();
如下调用就能发生死锁
ThreadA :----->锁住left---->尝试锁住right--->永久等待。
ThreadB :----->锁住right--->尝试锁住left--->永久等待。
根本原因在于leftRight和rightLeft方法获取锁的顺序不一样。如果每个需要left锁和right锁的线程都以相同的顺序来获取left锁和right锁则不存存在此问题。所以如果所有线程以固定的顺序来获取锁,那么在程序中就不会存在因为顺序造成的死锁问题。
所以我们在这里将rightLeft改成跟leftRight一样的顺序即可:
public void rightLeft()
synchronized(left)
synchronized(right)
doSomethingB();
二、动态的锁顺序死锁。
leftRight和rightLeft方法是两个固定写死的方法,所以修改他们很容易,只需要将顺序改变成一致即可。但是,如果是下面的代码呢?是否会发生死锁呢?
/**
* 转账方法
* @param fromAccount 谁转账
* @param toAccount 转账给谁
* @param amount 转账金额
*/
public void transferMoneyMoney(Account fromAccount,Account toAccount,DollarAmount amount)
synchronized(fromAccount)
synchronized(toAccount)
//执行转账逻辑
transferMoneyMoney方法的作用就是用来转账,参数定了转账账户、被转账账户和转账金额。乍一看没问题,但是仍然会造成死锁,死锁的原因跟leftRight和rightLeft一模一样,都是获取锁的顺序造成的:
ThreadA :transferMoneyMoney(X,Y)----->锁住X---->尝试锁住Y-->永久等待。
ThreadB :transferMoneyMoney(Y,X)------>锁住Y--->尝试锁住X--->永久等待。
前面说过只要所有线程以固定的顺序获取锁,那么就不会发生死锁,所以我们改造的方向也有了,就是要达成如下效果,也就是获取所得顺序不应该跟参数的顺序有关,而是固定顺序:
ThreadA :transferMoneyMoney(X,Y)----->锁住X---->尝试锁住Y-->转账成功。
ThreadB :transferMoneyMoney(Y,X)------>锁住X--->尝试锁住Y--->转账成功。
有了这个思路我们就可以如下改造:
/**
* 转账方法
* @param fromAccount 谁转账
* @param toAccount 转账给谁
* @param amount 转账金额
*/
public void transferMoneyMoney(Account fromAccount,Account toAccount,DollarAmount amount)
int fromHash = System.identityHashCode(fromAccount);
int toHash = System.identityHashCode(toHash);
if(fromHash>toHash)
synchronized(fromAccount)
synchronized(toAccount)
//执行转账逻辑
else if(fromHash<toHash)//说明参数顺序发生改变
synchronized(toAccount)
synchronized(fromAccount)
//执行转账逻辑
根据账号的HashCode大小来判断transferMoneyMoney(X,Y)还是transferMoneyMoney(Y,X).
我们假设xHash>yHash:
ThreadA :transferMoneyMoney(X,Y)----->锁住X---->尝试锁住Y-->转账成功。
ThreadB :transferMoneyMoney(Y,X)------>锁住X--->尝试锁住Y--->转账成功。
如果xHash<yHash,那么:
ThreadA :transferMoneyMoney(X,Y)----->锁住Y---->尝试锁住X-->转账成功。
ThreadB :transferMoneyMoney(Y,X)------>锁住Y--->尝试锁住X--->转账成功。
可以看出不管fromAccount和toAccount的两个参数怎么调换,都能保证ThreadA和ThreadB获取锁的顺序是一致的,不会造成死锁问题。
下面在处理下fromAccount.hashCode和toAccount.hashCode相等的情况,这个情况好判断,在加一把锁就可以了:
/**
* 转账方法
* @param fromAccount 谁转账
* @param toAccount 转账给谁
* @param amount 转账金额
*/
private static final Object lock = new Object();
public void transferMoneyMoney(Account fromAccount,Account toAccount,DollarAmount amount)
if(fromHash>toHash)
else if(fromHash<toHash)//说明参数顺序发生改变
else
synchronized(lock)
synchronized(fromAccount)
synchronized(fromAccount)
//执行转账逻辑
在获取两个账号的锁之前,首先需要获取lock锁,从而保证每次只有一个线程以未知的顺序获得者两个锁。
按照《Java并发编程实战》第10.4的测试程序,说上面这么代码仍然有潜在的死锁,但是个人水平有限,暂时没看出来。如果读者发现了,不妨在评论区告知。不胜感激。
以上是关于Java并发编程实战读书笔记之死锁的主要内容,如果未能解决你的问题,请参考以下文章