来源:blog.csdn.net/xiewenfeng520/article/details/107230996前言如果你只对死锁代码感兴趣,可以直接跳到不可避免的死锁例子的第三节。如果你对死锁了解不多,我们一起来探讨以下话题。什么是死锁?死锁的危害和特点是什么?该代码实现了一个不可避免的死锁示例。分析死锁的过程1.什么是死锁?关键词:并发场景,多线程首先,我们要知道并发场景一定会出现死锁。为了保证线程安全,我们有时会使用各种可以保证程序并发安全的工具,尤其是锁,但是如果在使用过程中处理不当,就有可能出现死锁。关键词:死锁是两个(或多个)线程(或进程)持有彼此需要的资源,但不主动释放自己持有的资源,导致每个人都得不到自己想要的资源的一种状态,所有相关的线程(或进程)都不能继续执行,直到这个状态发生改变,它们才能继续前进。我们称这种状态为死锁状态,认为他们已经死锁了。简而言之,死锁是两个或多个线程(或进程)无限期阻塞,等待彼此资源的状态。两个线程的死锁情况如图所示。线程1已经持有锁1,线程2也持有锁2,然后线程1试图获取锁2,但是线程2并没有释放锁2,所以线程1处于阻塞状态。同样,图中的线程2在获取到锁1的时候也会被阻塞。这样一来,线程1和线程2就产生了死锁,因为他们都持有着彼此想要的资源,却没有释放自己手中的资源,形成相互等待,并将继续等待。2.死锁的影响和危害2.1死锁的影响死锁的影响在不同的系统中是不同的,影响的大小部分取决于当前系统或环境处理死锁的能力。2.1.1在数据库中例如,在数据库系统软件的设计中,要考虑死锁的监控和死锁的恢复。在执行事务时,可能需要获取多个锁并持有这些锁,直到事务完成。一个事务中持有的锁在其他事务中也可能需要,因此两个事务之间可能会发生死锁。一旦发生死锁,如果没有外部干预,两个事务将一直等待下去。但是数据库系统不会允许这种情况发生。当数据库检测到这组事务发生死锁时,可能会根据不同的策略选择放弃某个事务,被放弃的事务会释放它持有的事务。锁,以便其他事务继续顺利进行。这时候程序可以重新执行那个被强行终止的事务,这个事务现在可以顺利执行了,因为刚才所有和它竞争资源的事务都已经执行完了,资源已经释放了。2.1.2在JVM中在JVM中,处理死锁的能力没有数据库强大。如果JVM发生死锁,JVM并不会自动处理,所以一旦发生死锁,就会陷入无休止的等待。2.2死锁的危害和特点关键词:概率事件死锁问题和其他并发安全问题一样,是概率性的,也就是说即使有死锁的可能,也不会100%的时间发生.如果每个锁持有的时间都比较短,那么发生冲突的概率就低,那么死锁的概率也低。但是在线系统中,每天可能会有数千万次的“获取锁”和“释放锁”操作。在数量庞大的情况下,整个系统出现问题的概率会被放大。有风险,可能会导致死锁。正是由于死锁“不一定发生”的特点,提前发现死锁就成了一个难题。压力测试虽然可以检测到一些可能发生死锁的情况,但它不足以完全模拟真实的长时间运行的场景,所以没有办法找出所有可能导致死锁的代码。关键词:危害大、发生概率低一旦发生死锁,根据死锁线程的职责不同,可能会造成子系统崩溃、性能下降,甚至整个系统崩溃等各种不良后果。而且,死锁往往发生在高并发、高负载的情况下,因为死锁可能会直接影响到很多用户,从而引发一系列的问题。以上就是死锁概率不高但是危害很大的特点。3.必死锁示例publicclassMustDeadLockDemo{publicstaticvoidmain(String[]args){Objectlock1=newObject();对象锁2=新对象();newThread(newDeadLockTask(lock1,lock2,true),"线程1").start();newThread(newDeadLockTask(lock1,lock2,false),"线路2").start();}staticclassDeadLockTaskimplementsRunnable{privatebooleanflag;私有对象锁1;私有对象锁2;publicDeadLockTask(Objectlock1,Objectlock2,booleanflag){this.lock1=lock1;这个.lock2=lock2;this.flag=标志;}@Overridepublicvoidrun(){if(flag){synchronized(lock1){System.out.println(Thread.currentThread().getName()+"->拿到锁1");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"->等待锁2释放...");synchronized(lock2){System.out.println(Thread.currentThread().getName()+"->GetLock2");}}}if(!flag){synchronized(lock2){System.out.println(Thread.currentThread().getName()+"->Getlock2");尝试{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"->waitingforlock1Release...");synchronized(lock1){System.out.println(Thread.currentThread().getName()+"->getlock1");}}}}}}执行结果:可以看到程序已被屏蔽4、流程分析其实上面代码例子中出现死锁的流程就是第一节中两个线程死锁的情况。这里我们拿图方便分析。本文使用IDEA调试,在第33行,run方法第一行设置断点,选择Thread模式。注意:在调试过程中,因为有人为的等待时间,所以不会出现死锁。这里只是演示线程执行的顺序和状态。第一步,线程1进入,flag=true,进入第一个synchronized同步块,获取lock1(锁1)第二步,直接点击ResumeProgram(F9),进入线程2,此时flag=false,进入secondsynchronized同步块,当然如果Thread.sleep时间足够长,或者运行速度足够快,也会出现死锁。5.小结本章我们讨论了什么是死锁,以及死锁的影响和危害,演示了一个不可避免的死锁的例子,然后使用IDEA工具调试了两个线程之间死锁的步骤。如果JVM发生死锁,可能导致部分甚至全部程序无法继续向下执行。所以JVM中死锁的危害和影响是比较大的,我们需要尽量避免。最后,如果大家在面试中遇到这个问题,希望大家能够顺利通过。参考:《Java 并发编程 78 讲》-徐龙熙近期热文推荐:1.1000+Java面试题及答案(2021最新版)2.别满屏if/else,试试策略模式,真香!!3.操!Java中xx≠null的新语法是什么?4、SpringBoot2.5发布,深色模式太炸了!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!
