当前位置: 首页 > 科技观察

死锁终结者:顺序锁和轮询锁!

时间:2023-03-22 14:18:17 科技观察

死锁(DeadLock)是指两个或多个计算单元(进程、线程或协程),都在等待对方停止执行以获取系统资源,但没有一个提前退出,称为死锁。死锁示例代码如下:publicclassDeadLockExample{publicstaticvoidmain(String[]args){ObjectlockA=newObject();//创建锁AObjectlockB=newObject();//创建锁B//创建线程1Threadt1=newThread(newRunnable(){@Overridepublicvoidrun(){synchronized(lockA){System.out.println("Thread1:GetlockA!");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("线程1:等待获取B...");synchronized(lockB){System.out.println("线程1:获取锁B!");}}}});t1.start();//运行线程//创建线程2Threadt2=newThread(newRunnable(){@Overridepublicvoidrun(){synchronized(lockB){System.out.println("线程2:获取锁B!");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("Thread2:waitingtogetA..??.");synchronized(lockA){System.out.println("Thread2:ObtainedlockA!");}}}});t2.start();//Runthread}}以上程序的执行结果如下:从上面的results,可以看出线程1和线程2都进入了死锁状态,都在等待对方释放锁。从上面例子的分析可以得出,死锁的发生需要满足以下四个条件:也就是说,在一段时间内,某个锁资源只能被一个计算单元占用。请求和保持条件:指操作单元至少保留了一个资源,但又提出新的资源请求,该资源已被其他操作单元占用。坚持,稍等。不可剥夺条件:指计算单元获得的资源,在用完之前不能被剥夺。循环等待条件:发生死锁时,必然存在计算单元和资源的循环链,即计算单元在等待另一个计算单元占用的资源,而另一方在等待自己占用的资源,导致循环等待案例。只有这4个条件同时满足,才会出现死锁问题。也就是说,要产生死锁,必须同时满足以上四个条件,那么我们可以通过破坏其中的任何一个条件来解决死锁问题。死锁解决方案分析接下来我们来分析死锁的4个条件,哪些是可以被破坏的?哪些不能被破坏?互斥条件:系统特性,不可破坏。请求和保留条件:可以销毁。不可剥夺条件:系统特性,不能被破坏。循环等待条件:可以被破坏。通过上面的分析,我们可以得出结论,只能通过破坏request和hold条件或者循环等待条件来解决死锁问题。当我们上线的时候,我们会从破坏“循环等待条件”开始解决死锁问题。方案一:顺序锁所谓顺序锁,就是指有序的获取锁,从而避免循环等待条件的产生,从而解决死锁问题。当我们不使用顺序锁时,程序的执行可能是这样的:线程1先获取锁A,然后获取锁B,线程2和线程1同时执行,线程2先获取锁B,然后获取到锁A,所以双方先占用各自的资源(锁A和锁B),然后再去尝试获取对方的锁,这样就造成了循环等待的问题,最后造成死锁问题。此时我们只需要统一线程1和线程2获取锁的顺序,即线程1和线程2同时执行后,都是先获取锁A,再获取锁B。执行过程如下图所示:因为只有一个线程能够成功获取到锁A,没有获取到锁A的线程会先等待获取锁A。此时获取锁A的线程继续获取锁B,因为没有线程在竞争和拥有锁B,那么获取锁A的线程就会成功拥有锁B,然后执行相应的代码和释放所有的锁资源,然后另一个等待获取锁A的线程可以成功获取锁资源并执行后续代码,这样就不会出现死锁问题。顺序锁的实现代码如下:publicclassSolveDeadLockExample{publicstaticvoidmain(String[]args){ObjectlockA=newObject();//创建锁AObjectlockB=newObject();//创建锁B//创建线程1Threadt1=newThread(newRunnable(){@Overridepublicvoidrun(){synchronized(lockA){System.out.println("Thread1:GetlockA!");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("线程1:等待获取B...");synchronized(lockB){System.out.println("线程1:获取锁B!");}}}});t1.start();//运行线程//创建线程2Threadt2=newThread(newRunnable(){@Overridepublicvoidrun(){synchronized(lockA){System.out.println("Thread2:GetlockA!");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("Thread2:waitingtogetB...");synchronized(lockB){System.out.println("线程2:获得锁B!");}}}});t2.start();//运行线程}}上面程序的执行结果如下:从上面的执行结果可以看出程序并没有出现死锁问题。方案二:轮询锁轮询锁通过打破“请求和保持条件”来避免死锁。它的实现思路很简单,就是尝试通过轮询的方式获取锁。释放当前线程拥有的所有锁,等待下一轮再次尝试获取锁。轮询锁的实现需要用到ReentrantLock的tryLock方法。具体实现代码如下:importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;publicclassSolveDeadLockExample{publicstaticvoidmain(String[]args){LocklockA=newReentrantLock();//创建锁ALocklockB=newReentrantLock();//创建锁B//创建线程1(使用轮询锁)Threadt1=newThread(newRunnable(){@Overridepublicvoidrun(){//调用轮询锁pollingLock(lockA,lockB);}});t1.start();//运行线程//创建线程2Threadt2=newThread(newRunnable(){@Overridepublicvoidrun(){lockB.lock();//加锁System.out.println("线程2:获取锁B!");try{Thread.sleep(1000);System.out.println("线程2:等待获取A...");lockA.lock();//locktry{System.out.println("线程2:获取锁A!");}finally{lockA.unlock();//释放锁}}catch(InterruptedExceptione){e.printStackTrace();}finally{lockB.unlock();//释放锁}}});t2.start();//运行线程}/***轮询锁*/publicstaticvoidpollingLock(LocklockA,LocklockB){while(true){if(lockA.tryLock()){//尝试获取锁System.out.println("Thread1:ObtainedlockA!");try{Thread.sleep(1000);System.out.println("线程1:等待获取B...");if(lockB.tryLock()){//尝试获取锁try{System.out.println("线程1:获取锁B!");}finally{lockB.unlock();//释放锁System.out.println("Thread1:ReleaselockB.");break;}}}catch(InterruptedExceptione){e.printStackTrace();}finally{锁A。unlock();//释放锁System.out.println("Thread1:ReleaselockA.");}}//等待一秒再继续try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}}}}上面程序的执行结果如下:从上面的结果可以看出上面的代码没有死锁问题。总结本文介绍两种解决死锁的方法:第一种顺序锁:通过改变获取锁的顺序,打破“循环请求条件”,避免死锁问题;第二种轮询锁:通过轮询,即打破“请求和占有条件”来解决死锁问题。它的实现思路是尝试通过自旋来获取锁。在获取锁的途中,如果任何一个锁获取失败,则释放之前获取的所有锁,等待一段时间后再次执行之前的流程,从而避免锁被占用的尴尬(一个线程),从而避免了死锁问题。参考资料和学分《Java并发编程实战》