在我们项目开发中,多线程问题是不可避免的,本章谈谈多线程死锁问题以及解决方案;
多线程环境中不可避免的要遇到线程死锁的问题。Java不像数据库那么能够检测到死锁,然后进行处理,Java中的死锁问题,只能通过程序员自己写代码时避免引入死锁的可能性来解决。
死锁产生的四个必要条件
- 互斥条件:即当资源被一个线程使用(占有)时,别的线程不能使用
- 不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 请求和保持条件:即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 循环等待条件:即存在一个等待队列: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
publicclassThreadResource{/** 资源1 和 资源2 */publicstaticObject resource1=newObject();publicstaticObject resource2=newObject();// public static Integer money1 = 45;// public static Integer money2 = 80;}
publicclassThread1implementsRunnable{// 模拟线程1持有(锁住)资源1,等待2秒获取(锁住)资源2@Overridepublicvoidrun(){try{System.out.println("Thread1 is running");synchronized(ThreadResource.resource1){System.out.println("Thread1 lock resource1");Thread.sleep(2000);//休眠2s等待线程2锁定资源2System.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");}}
publicclassThread2implementsRunnable{// 模拟线程2持有(锁住)资源2,等待2秒获取(锁住)资源1@Overridepublicvoidrun(){try{System.out.println("Thread2 is running");synchronized(ThreadResource.resource2){System.out.println("Thread2 lock resource2");Thread.sleep(2000);//休眠2s等待线程1锁定资源1synchronized(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");}}
publicclassThreadTest{publicstaticvoidmain(String[] args){newThread(newThread1()).start();newThread(newThread2()).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中的锁一般也是以对象为单位的,对象的内置锁保护对象中的数据的并发访问。所以如果我们能够避免在对象的同步方法中调用其它对象的同步方法,那么就可以避免死锁产生的可能性。如下所示的代码,就存在死锁的可能性:
packagecom.launch.sharedevice.elses;importjava.math.BigDecimal;publicclassThreadAccount1implementsRunnable{publicThreadAccount1(){}/** 转出账户 */privateAccount accountFrom=null;/** 转入账户 */privateAccount accountTo=null;/** 转账金额 */BigDecimal transferPrice=null;publicThreadAccount1(Account accountFrom,Account accountTo,BigDecimal transferPrice){this.accountFrom= accountFrom;this.accountTo= accountTo;this.transferPrice= transferPrice;}@Overridepublicvoidrun(){String threadName=Thread.currentThread().getName();try{System.out.println(threadName+"--开始转账");synchronized(accountFrom){System.out.println(threadName+"--锁定-转出账户>:"+accountFrom);Thread.sleep(2000);//休眠2s等待线程2锁定资源2synchronized(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);
}*/}packagecom.launch.sharedevice.elses;importjava.math.BigDecimal;publicclassThreadTest{publicstaticvoidmain(String[] args){// new Thread(new Thread1()).start();// new Thread(new Thread2()).start();// new Thread(new Thread3()).start();// new Thread(new Thread3()).start();Account accountFrom=newAccount();
accountFrom.setId(1L);
accountFrom.setName("小周");
accountFrom.setBalance(newBigDecimal(100));Account accountTo=newAccount();
accountTo.setId(2L);
accountTo.setName("小李");
accountTo.setBalance(newBigDecimal(90));BigDecimal transferPrice=newBigDecimal(30);newThread(newThreadAccount1(accountFrom, accountTo,transferPrice),"周转李").start();newThread(newThreadAccount1(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