死锁(DeadLock)是指两个或多个计算单元(进程、线程或协程),都在等待对方停止执行以获取系统资源,但没有一个提前退出,称为死锁。死锁示例代码如下:publicclassDeadLockExample{publicstaticvoidmain(String[]args){ObjectlockA=newObject();//创建锁A对象lockB=newObject();//创建锁B//创建线程1Threadt1=newThread(newRunnable(){@Overridepublicvoidrun(){synchronized(lockA){System.out.println("Thread1:GetlockA!");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("Thread1:waitingtogetB...");synchronized(lockB){System.out.println("线程1:获取锁B!");}}}});t1.开始();//运行线程//创建线程2Threadt2=newThread(newRunnable(){@Overridepublicvoidrun(){synchronized(lockB){System.out.println("线程2:获得锁B!");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("Thread2:WaitingtoacquireA...");synchronized(lockA){System.out.println("Thread2:AcquiredlockA!");}}}});t2.开始();//Runthread}}以上程序的执行结果如下:从上面的结果可以看出,线程1和线程2都进入了死锁状态,都在等待对方释放锁。通过以上例子的分析,可以得出死锁需要满足以下4个条件:互斥条件:指计算单元(进程、线程或协程)对分配的资源具有排他性,即,某个锁资源在一定时间内只能被一个计算单元使用。占据。请求和保持条件:指操作单元至少保留了一个资源,但又提出新的资源请求,该资源已被其他操作单元占用。坚持,稍等。不可剥夺条件:指计算单元获得的资源,在用完之前不能被剥夺。循环等待条件:发生死锁时,必然存在计算单元和资源的循环链,即计算单元在等待另一个计算单元占用的资源,而另一方在等待自己占用的资源,导致循环等待案例。只有这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();//创建锁A对象lockB=newObject();//创建锁B//创建线程1Threadt1=newThread(newRunnable(){@Overridepublicvoidrun(){synchronized(lockA){System.out.println("Thread1:LockAisacquired!");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("Thread1:waitingtogetB...");synchronized(lockB){System.out.println("线程1:获取锁B!");}}}});t1.开始();//运行线程//创建线程2Threadt2=newThread(newRunnable(){@Overridepublicvoidrun(){synchronized(lockA){System.out.println("线程2:获取锁A!");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();系统输出。println("线程2:等待获取B...");synchronized(lockB){System.out.println("线程2:获得锁B!");}}}});t2.开始();//Runningthread}}上面程序的执行结果如下:从上面的执行结果可以看出程序没有出现死锁问题解决方法二:轮询锁通过打破“请求”来避免轮询锁andholdcondition”导致死锁,其实现思路很简单,就是尝试通过轮询的方式获取锁。如果获取锁失败,则释放当前线程拥有的所有锁,等待下一轮再尝试获取锁。轮询锁的实现需要用到ReentrantLock的tryLock方法。具体实现代码如下:importjava.util.concurrent.locks.Lock;导入java.util.concurrent.locks.ReentrantLock;publicclassSolveDeadLockExample{publicstaticvoidmain(String[]args){锁lockA=newReentrantLock();//创建锁ALocklockB=newReentrantLock();//创建锁B//创建线程1(使用轮询锁)Threadt1=newThread(newRunnable(){@Overridepublicvoidrun(){//调用轮询锁pollingLock(lockA,lockB);}});t1.开始();//运行线程//创建线程2Threadt2=newThread(newRunnable(){@Overridepublicvoidrun(){lockB.lock();//lockSystem.out.println("Thread2:GetlockB!");try{Thread.sleep(1000);System.out.println("Thread2:waitingtogetA..??.");lockA.lock();//locktry{System.out.println("线程2:获取锁A!");}最后{lockA.unlock();//释放锁}}catch(InterruptedExceptione){e.printStackTrace();}最后{lockB.开锁();//释放锁}}});t2.开始();//运行线程}/***pollinglock*/publicstaticvoidpollingLock(LocklockA,LocklockB){while(true){if(lockA.tryLock()){//尝试获取锁System.out.println("线程1:获取锁A!");尝试{Thread.sleep(1000);System.out.println("线程1:等待获取B...");if(lockB.tryLock()){//尝试获取锁try{System.out.println("Thread1:ObtainedlockB!");}最后{lockB.unlock();//释放锁System.out.println("线程1:释放锁B。");休息;}}}catch(InterruptedExceptione){e.printStackTrace();}最后{lockA.unlock();//释放锁System.out.println("Thread1:ReleaselockA.");}}//等待一秒再继续执行try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}}}}上面程序的执行结果如下:从上面的结果可以看出上面的代码没有死锁问题。总结本文介绍两种解决死锁的方法:第一种顺序锁:通过改变获取锁的顺序,“循环请求”打破“条件”,避免死锁问题;第二种轮询锁:通过轮询,即,打破“请求和占有条件”来解决死锁问题,它的实现思路是尝试通过自旋来获取锁,在获取锁的途中,如果任何一个锁获取失败,则释放之前获取的所有锁,之前的过程等待一段时间后再次执行,这样就避免了锁已经被(线程)占用的尴尬,从而避免了死锁问题。参考&致谢《Java并发编程实战》并发原创文章推荐跟帖4种创建方法及详细使用说明!Java中的用户线程和守护线程有这么大的区别吗?深入理解ThreadPool线程池的7种创建方式,强烈推荐大家使用……池化技术到底有多牛?看到线程和线程池的对比,惊呆了!并发中的线程同步和locksynchronized锁this和class的区别!volatile和synchronized的区别轻量级锁就一定比重量级锁快吗?这样终止线程会导致服务宕机?SimpleDateFormat线程不安全的5个解决方案!ThreadLocal不好用?这就是你没用的原因!ThreadLocal内存溢出代码演示及原因分析!信号量告白:限流器我用对了!CountDownLatch:别招手了,等大家再次加入我们!CyclicBarrier:当所有人都准备好后,司机就可以发动汽车了!synchronized优化方法的锁扩展机制!synchronized中的4个优化,你知道几个?ReentrantLock的4个坑!图解:为什么非公平锁性能更高?4个解决死锁的工具!关注公众号“Java中文社区”,查看更多有趣的Java并发知识文章。
