当前位置: 首页 > 后端技术 > Java

ThreadLivenessFault

时间:2023-04-01 14:59:29 Java

目录死锁产生条件和死锁避免恢复信号LostLockNestedMonitorDeadlockThreadStarvationLivelock死锁产生条件和避免死锁必须满足以下所有条件资源互斥:资源必须互斥,即this资源只能被一个线程占用。当一个资源被使用时,如果该资源被其他线程占用,则需要等待其他线程释放该资源,而该线程并没有释放自己占用的资源。循环等待资源:每个线程都在请求等待其他资源,并不主动释放自己的资源。这些线程的请求形成一个闭环。T1占用资源A等待资源B被T2释放,同时不释放资源AT2占用资源B等待资源C被T3释放,自己不释放资源BT3占用资源C等待资源D被释放T4自己不释放资源CT4占用资源D,等待资源A被T1释放,资源D自己不释放。如果你想避免死锁,你可以通过消除上面任何一个条件来避免死锁。锁粗化:比如哲学家问题中,筷子只能独占。大家同时去拿筷子,然后哲学家们都同时左手拿起筷子,但是他们右边的筷子却被隔壁的哲学家拿在了左手上,然后他们都在等待隔壁哲学家放下左手的筷子。任何人都不能松手,形成僵局。以上相当于哲学家A、哲学家B、哲学家C分别获得了筷子A、筷子B、筷子C,相当于线程A、线程B、线程C分别获得了Lock-A、Lock-B、Lock-C!我把这个锁“粗”到我们每次只能让一个哲学家拿筷子,这样左右两根筷子都至少有一个哲学家拿,这样就不会出现循环等待资源的情况。其实就是用Lock来替代Lock-A、Lock-B、Lock-C,让同一时刻只能有一个线程获取资源,这样可以有效避免死锁,但是缺点很明显就是减少了并发性的可能性,造成资源的浪费!锁排序仍然是哲学家的难题。我们给筷子加上序列号,筷子A(1),筷子B(2),筷子C(3)。序号小,先拿哪根筷子。可能会出现以下场景:哲学家A先拿起筷子A(1),然后拿起筷子C(3),然后哲学家B发现自己右手的筷子A的序号较小,但已经被被哲学家A拿走了,所以他不能拿筷子B(2),然后哲学家C发现他右手的筷子B(2)的序号比他左手的筷子C(3)的序号小,但他需要等哲学家A放下筷子C(3)才能吃饭。以上哲学家的筷子会有很多排列,但是由于需要按照左右手的筷子序号来拿起筷子(每个哲学家的左手筷子的序号不是大于右手,并且每个哲学家右手筷子的序号不会大于左手)所以不会循环等待。使用tryLock(long,TimeUnit)申请锁。当一个线程申请资源的时间达到一定限度时,就会放弃申请锁,这样就不会造成循环等待资源的场景。比如哲学家的问题,如果每个哲学家右手都拿到了筷子,他的左手就在等着隔壁放下筷子,但是如果在一定的时间内他等不到筷子一次,他会放弃等待,放下右手的筷子。try{leftLock.tryLock()}catch(interruptedExceptione){rightLock.unLock();......}...死锁恢复在java中应用了内部锁或Lock.lock()锁,死锁是不可恢复的,这些死锁只能通过重启虚拟机来解除。但是如果线程是Lock.lockInterruptibly()申请的锁,那么死锁可能会通过调用thread.interrupt()来打破。publicclassDeadlockDetectorextendsThread{staticfinalThreadMXBeantmb=ManagementFactory.getThreadMXBean();/***检测周期(单位为每秒)*/privatefinallongmonitorInterval;publicDeadlockDetector(longmonitorInterval){super("DeadLockDetector");设置守护进程(真);this.monitorInterval=monitorInterval;}publicDeadlockDetector(){这(2000);}publicstaticThreadInfo[]findDeadlockedThreads(){long[]ids=tmb.findDeadlockedThreads();返回null==tmb.findDeadlockedThreads()?新的ThreadInfo[0]:tmb.getThreadInfo(ids);}publicstaticThreadfindThreadById(longthreadId){for(Threadthread:Thread.getAllStackTraces().keySet()){if(thread.getId()==threadId){返回线程;}}返回空值;}publicstaticbooleaninterruptThread(longthreadID){线程thread=findThreadById(threadID);if(null!=thread){thread.interrupt();返回真;}返回假;}@Overridepublicvoidrun(){ThreadInfo[]threadInfoList;线程信息ti;诠释我=0;try{for(;;){//检测系统是否存在死锁threadInfoList=DeadlockDetector.findDeadlockedThreads();if(threadInfoList.length>0){//选择任意死锁线程ti=threadInfoList[i++%threadInfoList.length];Debug.error(“检测到死锁,试图通过中断%n线程(%d,%s)%n”来恢复“+”,ti.getThreadId(),ti.getThreadName());//向选定的死锁线程发送中断DeadlockDetector.interruptThread(ti.getThreadId());继续;}else{Debug.info("没有发现死锁!");我=0;}Thread.sleep(monitorInterval);}//for循环结束}catch(InterruptedExceptione){//什么都不做;}}}通过调用管理。ThreadMXBean.findDeadlockedThreads()方法可以检测虚拟机中的死锁线程锁线程,然后打断这些线程的死锁,实现死锁恢复。由于死锁产生的原因不可控,死锁恢复的可操作性不强,甚至可能在死锁中断过程中产生活锁等新问题。信号丢失锁的一个典型例子是等待线程在执行Object.wait()/Condition.await()之前没有判断保护条件。没有其他线程更新相应保护条件涉及的共享变量使其建立并通知等待线程,这使得等待线程一直处于等待状态,使其任务无法一直进行。嵌套监视器死锁嵌套监视器死锁会导致活动失败,在这种情况下,线程永远不会被通知唤醒等待线程。公共类NestedMonitorDeadlockDemo{staticObjectlockA=newObject();静态对象锁B=新对象();publicstaticvoidmain(String[]args){Threadt1=newThread(){@Overridepublicvoidrun(){synchronized(lockA){synchronized(lockB){try{lockB.wait();}catch(InterruptedExceptione){e.printStackTrace();}}}}};Threadt2=newThread(){@Overridepublicvoidrun(){synchronized(lockA){synchronized(lockB){lockB.notifyAll();}}}};}}如上代码t1线程中的lockB.wait()表示t1线程释放了lockB,但是lockA不会被其他释放锁定,这样t2就永远无法获得lockA从而不会执行lockB.notifyAll().在这种情况下,t1永远不会被唤醒,t2线程会一直卡在lockA的获取中。嵌套监听死锁一般不会像上面代码那样“明显”出现,但一般在使用一些API时会出现,使用阻塞队列模拟一个消息队列,如下图,基于源码的深度拆解上图中getMsg方法和setMsg方法的代码,这两个锁的用法可以总结如下==getMsg==sychronized(NesredMonitorDeadLocalDemo.class){while(条件){notEmpty.await();}notFull.signal();lock.unlock();}==setMsg==sychronized(NesredMonitorDeadLocalDemo.class){lock.lockInterruptibly();while(条件){notFull.await();}notEmpty.signal();lock.unlock();}拆解后发现这是一个经典的嵌套监听死锁。当一个线程执行getMsg阻塞时,会释放显式锁并不会释放最外层的内部锁,其他线程在访问setMsg时永远不会获取到这个内部锁。线程饥饿线程饥饿(ThreadStarvation)是指一个线程一直无法获得它所需要的资源,导致它的任务一直无法进行的一种活动故障。活锁线程一直在运行,但是它执行的任务没有进行。结果它一直拥有线程却一直没有释放,干了一些无意义的事情。