多线程死锁以及解决方案

在我们项目开发中,多线程问题是不可避免的,本章谈谈多线程死锁问题以及解决方案;
多线程环境中不可避免的要遇到线程死锁的问题。Java不像数据库那么能够检测到死锁,然后进行处理,Java中的死锁问题,只能通过程序员自己写代码时避免引入死锁的可能性来解决。

死锁产生的四个必要条件
  1. 互斥条件:即当资源被一个线程使用(占有)时,别的线程不能使用
  2. 不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  3. 请求和保持条件:即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  4. 循环等待条件:即存在一个等待队列:T1占有T2的资源,T2占有T3的资源,T3占有T1的资源。这样就形成了一个等待环路。

1 Java中导致死锁的原因

Java中死锁最简单的情况是:线程1 (Thread1)持有 锁1(lock1)并且申请获得锁2(lock2),而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2永远被阻塞了。导致了死锁。这是最容易理解也是最简单的死锁的形式。

但是实际环境中的死锁往往比这个复杂的多。可能会有多个线程形成了一个死锁的环路,比如:线程T1持有锁L1并且申请获得锁L2,而线程T2持有锁L2并且申请获得锁L3,而线程T3持有锁L3并且申请获得锁L1,这样导致了一个锁依赖的环路:T1依赖T2的锁L2,T2依赖T3的锁L3,而T3依赖T1的锁L1。从而导致了死锁。

1.1 死锁demo
public class ThreadResource
{
	/** 资源1 和 资源2 */
    public static Object resource1 = new Object();
    public static Object resource2 = new Object();
//    public static Integer money1 = 45;
//    public static Integer money2 = 80;
}
public class Thread1 implements Runnable {
	// 模拟线程1持有(锁住)资源1,等待2秒获取(锁住)资源2
	@Override
    public void run() {
        try {
            System.out.println("Thread1 is running");
            synchronized (ThreadResource.resource1)
            {
                System.out.println("Thread1 lock resource1");
                Thread.sleep(2000);//休眠2s等待线程2锁定资源2
                System.out.println("Thread1 release resource1");
                synchronized (ThreadResource.resource2)
                {
                    System.out.println("Thread1 lock resource2");
                }
                System.out.println("Thread1 release resource2");
            }
        }
        catch (Exception e)  {
            System.out.println(e.getMessage());
        }
        System.out.println("Thread1 is stop");
    }
}
public class Thread2 implements Runnable {

    // 模拟线程2持有(锁住)资源2,等待2秒获取(锁住)资源1
    @Override
    public void run()  {
        try {
            System.out.println("Thread2 is running");
            synchronized (ThreadResource.resource2) {
                System.out.println("Thread2 lock resource2");
                Thread.sleep(2000);//休眠2s等待线程1锁定资源1
                
                synchronized (ThreadResource.resource1) {
                    System.out.println("Thread2 lock resource1");
                }
                System.out.println("Thread2 release resource1");
            }
            System.out.println("Thread2 release resource2");
            
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("Thread2 is stop");
    }
}
public class ThreadTest {
    public static void main(String[] args)  {
       new Thread(new Thread1()).start();
       new Thread(new Thread2()).start();
    }
}

/*
打印的结果>:
Thread1 is running
Thread2 is running
Thread1 lock resource1
Thread2 lock resource2
Thread1 release resource1
*/

根据打印结果,程序一直无法结束。这就是由于线程1占用了资源1,此时线程2已经占用资源2,。这个时候线程1想要使用资源2,线程2想要使用资源1,。两个线程都无法让步,导致程序死锁。

Java中如何避免死锁

既然我们知道了产生死锁可能性的原因,那么就可以在编码时进行规避。Java是面向对象的编程语言,程序的最小单元是对象,对象封装了数据和操作,所以Java中的锁一般也是以对象为单位的,对象的内置锁保护对象中的数据的并发访问。所以如果我们能够避免在对象的同步方法中调用其它对象的同步方法,那么就可以避免死锁产生的可能性。如下所示的代码,就存在死锁的可能性:

package com.launch.sharedevice.elses;

import java.math.BigDecimal;

public class ThreadAccount1 implements Runnable {
	
	public ThreadAccount1() { }
	/** 转出账户 */
	private Account accountFrom = null;
	/** 转入账户 */
	private Account accountTo = null;
	/** 转账金额 */
	BigDecimal transferPrice = null;
	
	public ThreadAccount1(Account accountFrom,Account accountTo,BigDecimal transferPrice) { 
		this.accountFrom = accountFrom;
		this.accountTo = accountTo;
		this.transferPrice = transferPrice;
	}
	@Override
	public void run() {
		
		String threadName = Thread.currentThread().getName();
        try {
            System.out.println(threadName+"--开始转账");
        	synchronized (accountFrom)
            {
                System.out.println(threadName+"--锁定-转出账户>:"+accountFrom);
                Thread.sleep(2000);//休眠2s等待线程2锁定资源2
                synchronized (accountTo)
                {
                	System.out.println(threadName+"--锁定-转入账户>:"+accountTo);
                	// 转账逻辑操作
                	accountFrom.setBalance(accountFrom.getBalance().subtract(transferPrice));
                	accountTo.setBalance(accountTo.getBalance().add(transferPrice));
                    
                }
                System.out.println(threadName+"--释放-转入账户>:"+accountTo);
            }
        	System.out.println(threadName+"--释放-转出账户>:"+accountFrom);
            
        }
        catch (Exception e)  {
            System.out.println(e.getMessage());
        }
        
        System.out.println(threadName+".result.accountFrom>:"+accountFrom);
        System.out.println(threadName+".result.accountTo>:"+accountTo);
        
		
		
	}
	
	
	
	
	
	/*// 线程1持有(锁住)资源1,等待2秒获取(锁住)资源2
	@Override
    public void run() {
		String threadName = Thread.currentThread().getName();
        try {
            System.out.println(threadName+" is running");
            
            if (accountFrom.getId() > accountTo.getId()) {
            	synchronized (accountFrom)
                {
                    System.out.println(threadName+" lock accountFrom>:"+accountFrom);
                    Thread.sleep(2000);//休眠2s等待线程2锁定资源2
                    System.out.println(threadName+" release accountFrom");
                    synchronized (accountTo)
                    {
                    	// 转账金额
                    	BigDecimal transferPrice = new BigDecimal(20);
                    	
                    	accountFrom.setBalance(accountFrom.getBalance().subtract(transferPrice));
                    	accountTo.setBalance(accountTo.getBalance().add(transferPrice));
                    	
                        System.out.println(threadName+" lock accountTo>:"+accountTo);
                    }
                    System.out.println(threadName+" release accountTo");
                }
            } else {
            	synchronized (accountTo)
                {
                    System.out.println(threadName+" lock accountTo>:"+accountTo);
                    Thread.sleep(2000);//休眠2s等待线程2锁定资源2
                    System.out.println(threadName+" release accountTo");
                    synchronized (accountFrom)
                    {
                    	// 转账金额
                    	BigDecimal transferPrice = new BigDecimal(20);
                    	
                    	accountTo.setBalance(accountTo.getBalance().subtract(transferPrice));
                    	accountFrom.setBalance(accountFrom.getBalance().add(transferPrice));
                    	
                        System.out.println(threadName+" lock accountFrom>:"+accountFrom);
                    }
                    System.out.println(threadName+" release accountFrom");
                }
            }
            
            
        }
        catch (Exception e)  {
            System.out.println(e.getMessage());
        }
        System.out.println(threadName+" is stop");
        
        System.out.println(threadName+".result.accountFrom>:"+accountFrom);
        System.out.println(threadName+".result.accountTo>:"+accountTo);
        
    }*/
	
}


package com.launch.sharedevice.elses;

import java.math.BigDecimal;

public class ThreadTest
{
    public static void main(String[] args)
    {
//       new Thread(new Thread1()).start();
//       new Thread(new Thread2()).start();
//       new Thread(new Thread3()).start();
//       new Thread(new Thread3()).start();
    	
    	
    	Account accountFrom = new Account();
    	accountFrom.setId(1L);
    	accountFrom.setName("小周");
    	accountFrom.setBalance(new BigDecimal(100));
    	
    	Account accountTo = new Account();
    	accountTo.setId(2L);
    	accountTo.setName("小李");
    	accountTo.setBalance(new BigDecimal(90));
    	
    	BigDecimal transferPrice = new BigDecimal(30);
    	new Thread(new ThreadAccount1(accountFrom, accountTo,transferPrice),"周转李").start();
    	new Thread(new ThreadAccount1(accountTo, accountFrom,transferPrice),"李转周").start();
    	
    	
    	
    	
    }
}


https://www.cnblogs.com/wy697495/p/9757982.html

https://www.jianshu.com/p/b9e8739c40e4

https://blog.csdn.net/ls5718/article/details/51896159

https://www.cnblogs.com/digdeep/p/4448148.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值