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

面试官让我说说线程的WAITING状态,我笑了

时间:2023-04-01 13:42:52 Java

面试官问:说说线程状态下的WAITING状态,什么时候会是这个状态?什么时候离开这个状态?小菜J会心一笑……一个无限等待另一个线程执行特殊动作的线程处于WAITING状态。无限期等待另一个线程执行特定操作的线程处于此状态。不过,具体是什么“特别行动”,并没有详细说明。详细定义见javadoc(jdk8):一个线程进入WAITING状态是因为调用了以下方法:Object.waitmethodwithouttimelimitThread.joinmethodwithouttimelimitLockSupport.park然后会等待其他线程执行特殊操作,例如:调用对象的Object.wait方法线程等待另一个线程对该对象调用Object.notify()或Object.notifyAll()。调用Thread.join方法的线程等待指定线程完成。对应的英文如下:Athreadisinthewaitingstateduetocallingoneofthefollowingmethods:Object.waitwithnotimeoutThread.joinwithnotimeoutLockSupport.park一个处于等待状态的线程正在等待另一个线程执行一个特定操作。例如,已对对象调用Object.wait()的线程正在等待另一个线程对该对象调用Object.notify()或Object.notifyAll()。已调用Thread.join()的线程正在等待指定线程终止。线程间的协作机制显然,WAITING状态不是一个线程的独角戏,而是涉及多个线程。协作机制。说到线程,我们往往想到的是线程之间的竞争(race),比如争夺锁,但这还不是全部,线程之间还会有合作机制。就像你和公司的同事一样,你们可能会为了晋升而竞争,但更多的时候你们是为了完成某些任务而共同努力的。wait/notify是线程间的一种协作机制,那么首先,为什么要等待呢?什么时候等?为什么它要等待其他线程执行“特殊操作”?它解决了什么问题?等待场景首先,为什么要等待?简单的说,就是因为条件(condition)不满足。那么什么是条件呢?为了方便理解,我们想象一个场景:有一个火车车厢,里面有很多乘客,每个乘客相当于一个线程;里面有个马桶,是公共资源,一次只允许一个线程访问(毕竟没人希望在马桶的时候跟别人分享~)。竞争关系如果有多个乘客同时想上厕所,那么这里首先存在的就是竞争关系。如果把马桶看成一个对象,它有一把锁,想上厕所的乘客线程需要在进入马桶之前获取一把锁。Java在语言层面直接提供了一种同步机制,即synchronized关键字:synchronized(expression){...}它的机制是这样的:计算表达式(expression)(值的类型必须是引用类型(referencetype)),获取其代表的对象,然后尝试获取该对象的锁:如果能获取到锁,则进入同步块执行,执行完退出同步块,返回对象的锁(异常退出也会返回);如果获取不到锁,就阻塞在这里,直到获取到锁。当一个线程还在厕所里的时候,其他同时想上厕所的线程都被阻塞了,在厕所对象的入口集合中,处于BLOCKED状态。完成后,离开厕所并归还锁。之后,系统在条目集中选择一个线程,并给它加锁。对于以上过程,下面是一个gif动图演示:当然,这就是我们熟悉的锁竞争过程。下面是演示代码:@TestpublicvoidtestBlockedState()throwsException{classToilet{//toiletclasspublicvoidpee(){//peemethodtry{Thread.sleep(21000);//研究表明,动物,不管大小小便时间大约是21秒}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}}厕所toilet=newToilet();Threadpassenger1=newThread(newRunnable(){publicvoidrun(){synchronized(toilet){toilet.pee();}}});Threadpassenger2=newThread(newRunnable(){publicvoidrun(){synchronized(toilet){toilet.pee();}}});passenger1.start();//确保乘客1启动Thread.sleep(100);passenger2.start();//确保run方法已经执行Thread.sleep(100);//当乘客1在上厕所期间,乘客2处于BLOCKED状态assertThat(passenger2.getState()).isEqualTo(Thread.State.BLOCKED);}condition现在,假设有一个女乘客在抢进门后锁上,脱下她的半条裤子。发现马桶垫纸没了,便不肯撒尿。可能是她比较讲究卫生吧,怕坐上去弄脏自己的白屁股~现在,情况出现了:有纸没纸,这是一定条件。那么,现在条件不满足,这个女线程该怎么办呢?如果只是在里面等着,显然是行不通的。这不正是人民所深恶痛绝的“占厕不便”吗?一方面,外面的入口集里可能还有很多人还在等着尿尿(可能有很多老主线程,他们才不在乎有没有厕纸~)另一方面,假设外面同时有“管家线”,它们要进去加Gasket纸,但是你出不来,别人也进不去,所以你加不了纸。所以当条件不满足的时候,就需要出来把锁还回去,这样“管家线程”之类的东西才能进去加纸。等待是必要的吗?那么出来之后还要等吗?当然不一定。这里所谓的“等待”是指让线程处于不活动状态,即从调度队列中移除。如果不等待,直接把锁归还,用一个repeatedloop判断条件是否满足,就可以再次回到dispatchqueue,然后期待下一次dispatch的时候条件可能已经改变了时间:比如某位“空姐线程”之前已经出动过,并在里面增加了垫片纸。自然,也有可能再次派出时,条件还是不满足。现在再考虑一个更极端的情况:厕所外有很多“女客丝”想进去方便,同时有一个心急如焚的“空姐丝”想进去加卫生纸。如果线程没有等待,厕所是公共资源,就不能并发访问。调度器每选择一个线程进入,就会降低选择“管家线程”的概率。很可能入口集聚集的越多,越方便的“女客线程”无法完成,“管家线程”被选中执行的概率就变高了。衰退。当然,同步机制会防止所谓的“饥饿”现象,“管家线程”最后还是有机会执行的,只是系统运行效率会下降。因此,这会干扰线程的正常工作,占用资源,影响自身条件的满足。另外,这段时间“客串”可能根本就没有启动。此时不想等待的“女客线程”只是白进白出,占用CPU资源却不做业务。实际上,它仍然在这种非递进式的进出中等待,类似于所谓的忙等待(busywaiting)。协作关系综上所述,等待还是有必要的,我们需要一个更高效的机制,即wait/notify协作机制。当不满足条件时,应该调用wait()方法。这时,线程释放锁,进入所谓的等待集。具体来说,就是进入厕所对象的waitset:此时线程不再活跃,不能再次参与调度,所以不会浪费CPU资源,也不会竞争锁。此时线程状态为WAITING。现在的问题是:他们什么时候才能再次搬家?显然,最好的时机就是条件具备的时候。之后,“空姐线程”进去添加卫生纸。当然,这个时候,也不能简单的加卫生纸了。它还需要执行一个特殊的动作,即在这个对象上“通知”女乘客的等待线程:可能是在对他们喊:“有纸!去尿尿!”显然,如果“女客串”只是一厢情愿地等待,她们是没有机会执行的。所谓“通知”就是将它们从waitset中释放出来,重新进入dispatchqueue(就绪队列)。如果是notify,则选择释放被通知对象的等待集中的一个线程;如果是notifyAll,则释放被通知对象的等待集上的所有线程。整个过程如下图所示:对于上面的过程,我们还给出如下gif动图演示:注意:即使只有一个等待线程被通知,被通知的线程也不能立即恢复执行,因为她中断的地方被synchronousblock,此时她已经不持有锁了,需要重新尝试获取锁(可能面临其他线程的竞争),只有成功后才能在调用wait方法后的地方继续执行.(这也就是所谓的“调用Object.wait后重入”)如果能够获取到锁,线程就会从WAITING状态变为RUNNABLE状态;否则,在退出等待集并进入入口集后,线程将从WAITING状态变为BLOCKED状态。综上所述,这是一种协作机制,“女客线程”和“管家线程”之间存在协作关系。显然,有了这种合作关系的存在,“女客线程”可以避免在条件不满足时的盲目尝试,也为“管家线程”的顺利执行腾出了资源;通知。合作关系的存在可以互惠互利。生产者和消费者的问题不难发现。上面本质上是一个经典的“生产者与消费者”问题:空姐线程生产卫生纸,女乘客线程消费卫生纸。当没有卫生纸(条件不满足)时,女乘客线程等待,空姐线程添加卫生纸(使条件满足),并通知女乘客线程(释放其等待状态)。接下来,女客线程能否进一步执行取决于锁的获取。代码演示:在下面的代码中,演示了上面的等待/通知过程:@TestpublicvoidtestWaitingState()throwsException{classToilet{//toiletclassintpaperCount=0;//paperpublicvoidpee(){//peemethodtry{Thread.sleep(21000);//研究表明,无论大小,动物大约在21秒内撒尿}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}}厕所toilet=newToilet();//两个乘客线程Thread[]passengers=newThread[2];for(inti=0;i