java中如何造成死锁的?以及如何解决死锁

造成死锁的原因:

1.在一个代码块中同时获得多个锁,导致多个线程同时执行代码时,获取锁之间相互依赖,从而导致锁“抱死”。例如,t1线程首先获得A锁,再获得B锁,t2线程先获得B锁,再获得A锁,当t1获得A锁的同时,t2获得了B锁的使用权,此时t1无法获取B锁,t2也无法获得A锁,线程一直等待,这就叫锁“抱死”。

2.在同步代码块中调用了外部的同步方法(常见)

下面我将结合实际情况分析账户转账时,发生死锁的代码示例。

针对第一种造成死锁的原因,嵌套多个synchronized同步。

首先建一个Account类,包括账户ID,账户名称,账户余额。


public class Account {

	private int id;
	private String accName;
	private double balance = 100;

	public Account(String name) {
		this.accName = name;
	}
	
	public Account(int id,String name) {
		this.id = id;
		this.accName = name;
	}
	
	public int compareTo(double money){
		if(balance > money){
			return 1;
		}else if(balance == money){
			return 0;
		}else{
			return -1;
		}
	}
	
	public void in(double money){
		balance += money;
		System.out.println(accName+"中的余额:"+balance);
	}
	
	public void out(double money){
		balance -= money;
		System.out.println(accName+"中的余额:"+balance);
	}
	
	/**
	 * @return the accName
	 */
	public String getAccName() {
		return accName;
	}

	/**
	 * @param accName the accName to set
	 */
	public void setAccName(String accName) {
		this.accName = accName;
	}

	/**
	 * @return the balance
	 */
	public double getBalance() {
		return balance;
	}

	/**
	 * @param balance the balance to set
	 */
	public void setBalance(double balance) {
		this.balance = balance;
	}

	/**
	 * @return the id
	 */
	public int getId() {
		return id;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(int id) {
		this.id = id;
	}
	
	
	
}

实现转账的方法:

	/**
	 * 造成死锁的示例:通过synchronized同步代码块
	 * @param A
	 * @param B
	 * @param money
	 * @throws Exception
	 */
	public static void transferMoney(Account A,Account B,double money) throws Exception{
		if(A.compareTo(money) < 0){
			throw new Exception("余额不足!");
		}
		synchronized(A){
			System.out.println(Thread.currentThread().getName()+"获得了"+ A.getAccName() +"的锁");
			Thread.sleep(2000);//模拟2个线程同时进行,等待获得B锁,这样就造成了死锁
			synchronized (B) {
				System.out.println("获得了"+ B.getAccName() +"的锁");
				A.out(money);
				B.in(money);
			}
		}
	}

测试方法:

	public static void main(String[] args) {
		final Account[] accounts = new Account[2];
		accounts[0] = new Account(1,"zhangsan");
		accounts[1] = new Account(2,"lisi");
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					transferMoney(accounts[0], accounts[1], 10);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					transferMoney(accounts[1], accounts[0], 10);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
		
	}

测试结果:

问题的原因开头已经提到了,zhangsan账户向lis账户转账首先获取了zhangsan的对象锁,lis同时向zhangsan转账获得了lisi的对象锁,此时zhangsan无法获得zhangsan的对象锁,lisi无法获取到zhangsan的对象锁,从而导致死锁。那么问题原因知道了,如何解决呢?

通过hashcode给对象锁排序,使得每次获取锁的顺序是一致的。这里对象的hashcode一般情况下是不一样的,如果为了保险起见可以使用对象的唯一ID进行排序也是可以的。

	/**方法一
	 * 解决死锁的方法:根据hashcode给锁排序,按照一个指定的顺序加锁
	 * @param A
	 * @param B
	 * @param money
	 * @throws Exception
	 */
	public static void solveDeadLock1(Account A,Account B,double money) throws Exception{
		if(A.compareTo(money) < 0){
			throw new Exception("余额不足!");
		}
		if(A.hashCode()<B.hashCode()){
			synchronized(A){
				System.out.println(Thread.currentThread().getName()+"获得了"+ A.getAccName() +"的锁");
				Thread.sleep(2000);//模拟2个线程同时进行,等待获得B锁,这样就造成了死锁
				synchronized (B) {
					System.out.println("获得了"+ B.getAccName() +"的锁");
					A.out(money);
					B.in(money);
				}
			}
		}else{
			synchronized(B){
				System.out.println(Thread.currentThread().getName()+"获得了"+ A.getAccName() +"的锁");
				Thread.sleep(2000);//等待获得B锁,这样就造成了死锁
				synchronized (A) {
					System.out.println("获得了"+ B.getAccName() +"的锁");
					A.out(money);
					B.in(money);
				}
			}
		}
		
	}
	
	/**方法二
	 * 解决死锁的方法:根据对象的唯一值进行排序
	 * @param A
	 * @param B
	 * @param money
	 * @throws Exception
	 */
	public static void solveDeadLock2(Account A,Account B,double money) throws Exception{
		if(A.compareTo(money) < 0){
			throw new Exception("余额不足!");
		}
		if(A.getId()<B.getId()){
			synchronized(A){
				System.out.println(Thread.currentThread().getName()+"获得了"+ A.getAccName() +"的锁");
				Thread.sleep(2000);//模拟2个线程同时进行,等待获得B锁,这样就造成了死锁
				synchronized (B) {
					System.out.println("获得了"+ B.getAccName() +"的锁");
					A.out(money);
					B.in(money);
				}
			}
		}else{
			synchronized(B){
				System.out.println(Thread.currentThread().getName()+"获得了"+ A.getAccName() +"的锁");
				Thread.sleep(2000);//等待获得B锁,这样就造成了死锁
				synchronized (A) {
					System.out.println("获得了"+ B.getAccName() +"的锁");
					A.out(money);
					B.in(money);
				}
			}
		}
		
	}

执行结果:

针对第二种造成死锁的原因,同步代码块中调用外部的同步方法

转账方法修改为同步方法:

	public synchronized void in(double money){
		balance += money;
		System.out.println(accName+"中的余额:"+balance);
	}
	/**
	 * 造成死锁的示例:通过synchronized同步方法
	 * @param A
	 * @param B
	 * @param money
	 * @throws Exception
	 */
	public static void transferMoney1(Account A,Account B,double money) throws Exception{
		if(A.compareTo(money) < 0){
			throw new Exception("余额不足!");
		}
		synchronized(A){
			System.out.println(Thread.currentThread().getName()+"获得了"+ A.getAccName() +"的锁");
			Thread.sleep(2000);//模拟2个线程同时进行,
			A.out(money);
			B.in(money);
		}
	}

测试结果:

原因和第一种是一样的,只是将同步代码块换成了同步方法,我们会发现zhangsan和lisi的账户中都少了10元,所以请记住一点:避免在同步代码块中调用外部的同步方法。

解决方法是尽量让原子操作的范围最小化。

实际上在转账过程中变量就是余额balance,而且balance是在in和out方法里计算的,所以将转入in方法和转出out方法都写为同步方法。

	public synchronized void in(double money){
		balance += money;
		System.out.println(accName+"中的余额:"+balance);
	}
	
	public synchronized void out(double money){
		balance -= money;
		System.out.println(accName+"中的余额:"+balance);
	}
	/**
	 * 解决死锁的方法:将原子操作的范围最小化,避免在同步代码块中调用外部的同步方法
	 * @param A
	 * @param B
	 * @param money
	 * @throws Exception
	 */
	public static void solveDeadLock3(Account A,Account B,double money) throws Exception{
		if(A.compareTo(money) < 0){
			throw new Exception("余额不足!");
		}
		A.out(money);
		B.in(money);
	}

测试结果:

总结

1.避免在同步代码块中调用外部的同步方法。

2.在嵌套多层synchronized同块中,对锁进行排序,使得每次获取锁的顺序是一致的。

猜你喜欢

转载自blog.csdn.net/ZixiangLi/article/details/88714722