1.什么是死锁?当两个或多个线程在执行时,由于争夺资源,它们处于相互等待的状态。循环的锁依赖永远等待。如果没有外部干预,他们将永远等待。此时的这种状态称为死锁。经典的“哲学家就餐”问题很好地描述了僵局的情况:5位哲学家去吃中餐,坐在一张圆桌旁,他们有5根筷子(而不是5双),并放在每两个人之间。一根筷子,哲学家要么在思考,要么在吃饭,每个人都需要一双筷子吃饭,吃完后把筷子放回原处继续思考,一些筷子管理算法(1)可以让每个人都可以相对的吃饭及时,但有些算法可能会导致部分或全部哲学家“饿死”。人们已经拥有的资源,每个人都不会放弃自己拥有的资源,直到他获得了他所需要的所有资源。筷子管理算法(一):一个饥饿的科学家会尝试去拿两根相邻的筷子,但是如果其中一根正被另一位科学家使用,那么他就会放弃自己已经拿到的筷子,等待几分钟再尝试死锁:大家立即拿起左手筷子,等待右手筷子腾出,但同时已经拿到的筷子也不放下,形成相互等待的状态。饥饿:哲学家们同时都想吃饭,同时拿起左手的筷子,却发现右边没有筷子,于是哲学家们同时放下了左手的筷子次,然后大家又发现有筷子,开始同时拿起左手的筷子,同时又放下,然后重复。当线程A持有锁L想要获取锁M,而线程B持有锁M想要获取锁L,那么这两个线程就会一直等待下去。这种情况是死锁(或“死锁”)的一种形式2.死锁的四个必要条件互斥条件:指一个进程独占使用所分配的资源,即某一资源在其内只被一个进程占用一段时间。如果此时有其他进程在请求资源,请求者只能等到占用资源的进程用完释放。请求和持有条件:表示一个进程至少保留了一个资源,但是又提出了新的资源请求,并且该资源已经被其他进程占用。此时,请求进程被阻塞,但它仍然持有它已经获得的其他资源。非剥夺条件:指进程已经获取的资源,在用完之前不能剥夺,用完只能自己释放。循环等待条件:发生死锁时,必然有一个进程——资源循环链,即进程集{A,B,C,...,Z}中的A正在等待B占用的资源;B在等待一个被C占用的资源,...,Z在等待一个已经被A占用的资源。3.死锁实例/***死锁类示例*/publicclassDeadLockimplementsRunnable{publicintflag=1;//静态对象是privatestaticObjecto1=newObject(),o2=newObject();@Overridepublicvoidrun(){System.out.println("flag:{}"+flag);if(flag==1){//先锁o1,再锁o2,循环等待条件synchronized(o1){try{Thread.sleep(500);}catch(Exceptione){e.printStackTrace();}synchronized(o2){System.out.println("1");}}}if(flag==0){//先锁o2,锁01synchronized(o2){try{Thread.sleep(500);}catch(Exceptione){e.printStackTrace();}synchronized(o1){System.out.println("0");}}}}publicstaticvoidmain(String[]args){DeadLocktd1=newDeadLock();DeadLocktd2=newDeadLock();td1.flag=1;td2.flag=0;//td1,td2都处于可执行状态,但JVM线程调度不确定哪个线程先执行。//td2的run()可能先于td1的run()newThread(td1).start();newThread(td2).start();}}1.当DeadLock类(td1)的对象flag=1时,首先locko1,sleep500毫秒2,td1在休眠的时候,另外一个flag==0的对象(td2)线程启动,先locko2,sleep500毫秒3,td1休眠后,需要locko2才能继续执行,此时o2已经被td2锁定;4、td2休眠后,o1需要加锁才能继续执行,此时o1已经被td1加锁;5、td1和td2相互等待,都需要获取对方锁定的资源才能继续执行。从而陷入僵局。动态锁序列死锁://资金转入账户publicstaticvoidtransferMoney(AccountfromAccount,AccounttoAccount,DollarAmountamount)throwsInsufficientFundsException{//锁定汇款人账户synchronized(fromAccount){//锁定收款人账户synchronized(toAccount){//判断余额theaccountcannotbenegativeif(fromAccount.getBalance().compareTo(amount)<0){thrownewInsufficientFundsException();}else{//汇款人的账户减钱fromAccount.debit(amount);//收款人的AddmoneytoAccount.credit(amount);}}}}上面的代码好像是按照相同的顺序获取锁,这是合理的,但是上面代码中加锁的顺序取决于传递给transferMoney()的参数的顺序,其中turndependsonexternalinputs如果两个线程(A和B)同时调用transferMoney()并且一个线程(A)从X向Y转账:transferMoney(myAccount,yourAccount,10);另一个线程(B)从Y向X转账:transferMoney(yourAccount,myAccount,20);此时线程A可能获取了myAccount的锁,等待yourAccount的锁,但是此时线程B已经持有yourAccount的锁,正在等待myAccount的锁,此时就发生了死锁。当一组Java线程死锁时,这些线程将永远无法再使用。根据线程所做的工作,它可能会导致应用程序完全停止,或者某个特定的子系统无法再使用,或者性能下降。此时恢复应用程序的唯一方法是暂停并重新启动它。死锁的影响很少立即显现出来。如果一个类发生死锁,并不意味着它每次都会发生,而只是意味着机会是,当死锁出现时,它往往是在最糟糕的时间——高负载下。4.死锁的避免和检测4.1防止死锁破坏互斥条件:使资源同时访问而不是互斥,这样就没有进程阻塞在资源上,所以不会发生死锁。销毁请求和保留条件:静态分配静态分配的方式是指进程在执行前必须申请所有需要的资源,直到所有需要的资源都满足后才会开始执行。只要一个资源不能分配,其他资源就不会分配给这个进程。H.销毁非剥夺条件:即当一个进程获得了一些资源但不能获得其他资源时,释放占用的资源,但只适用于内存和处理器资源。破坏循环等待条件:给系统所有的资源编号,规定进程请求所需资源的顺序必须按照资源的编号顺序。4.2设置锁顺序如果有两个线程(A和B),当A线程已经锁定了Z,然后尝试锁定X,而X已经被线程B锁定,线程A和线程B分别持有对应的A就会发生死锁当竞争另一个锁时(试图加锁一个已经被另一个线程加锁的锁),如下图所示:两个线程以不同的顺序尝试获取同一个锁,如果Request以相同的顺序加锁,那么就会出现没有循环锁依赖,所以不会出现死锁,每个需要锁Z和锁X的线程都会按照相同的顺序获取Z和X,那么就不会出现死锁,如下图所示:,死锁永远不会发生。对于特定的两把锁,可以尝试按照锁对象的hashCode值的顺序分别获取两把锁,这样锁总会按照特定的顺序获取。我们可以通过设置锁的顺序来防止死锁,这里我们使用System.identityHashCode方法来定义锁的顺序,该方法会返回Object.hashCode返回的值,这样就可以消除死锁的可能性。publicclassDeadLockExample3{//超时锁,在极少数情况下,如果两个哈希值相等,则使用此锁加锁privatestaticfinalObjecttieLock=newObject();publicvoidtransferMoney(finalAccountfromAcct,finalAccounttoAcct,finalDollarAmountamount)throwsInsufficientFundsException{classHelper{publicvoidthrowstransfers(){if(fromAcct.getBalance().compareTo(金额)<0)thrownewInsufficientFundsException();else{fromAcct.debit(金额);toAcct.credit(金额);}}}//得到两把锁的hash值intfromHash=System.identityHashCode(fromAcct);inttoHash=System.identityHashCode(toAcct);//根据hash值判断锁序,如果是则判断锁序(fromHash
