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

这样理解线程的生命周期是不是很容易?

时间:2023-03-20 22:02:34 科技观察

为什么要了解线程的生命周期?之前写过SpringBean生命周期三部曲:SpringBean生命周期的起源SpringBean生命周期的终结SpringAware到底是什么?有朋友留言说:“了解了它们的生命周期后,使用SpringBean就像看到了它们的动作轨迹,现在用起来一点都不慌。”像他一样,理解事物的生命周期的目的是很简单的,但是[不要慌]我写了很多Java并发系列,从来没提过[Java线程生命周期]。有了序言中理论图文的铺垫,在进入源码世界之前,说说的时机正好。因为,写并发程序的核心之一就是正确摆弄线程生命周期的几种状态。不说他们之间的状态是怎么转换的。原因是我把操作系统的通用线程状态和编程语言封装的线程状态搞混了。个人认为操作系统通用的线程状态更符合我们的思维习惯。一共有5种状态(如下图所示)。对于经常写并发程序的同学来说,经常看的就是操作系统中的这些通用的线程状态,看看birth【初始状态】和death【终止状态】,其实就是三种状态的各种转换,没听到这句话是不是觉得轻松了很多?为了更好的解释通用线程状态和Java语言中的线程状态,这里对前者做一个简单的说明。初始状态线程已创建,但不允许分配CPU执行。注意,这个创作其实是属于编程语言层面的。其实在操作系统中,还没有创建真正的线程,比如Java语言中的newThread()。处于可运行状态的线程可以分配CPU来执行。此时操作系统中的线程已经创建成功。在运行状态下,操作系统会为处于可运行状态的线程分配CPU时间片。被CPU加持后,处于runnable状态。线程会转变为运行状态和休眠状态。如果处于运行状态的线程调用了阻塞的API或者等待事件条件可用,线程将切换到休眠状态。注意:此时线程会释放CPU使用权,休眠线程永远没有机会获得CPU使用权。只有当等待事件发生时,线程才会从休眠状态切换到可运行状态。)会进入终止状态,正式走到生命的尽头,没有起死回生的机会接下来,我们就来看看大家熟悉的和不熟悉的,也经常会出现的Java线程的生命周期在采访中被问到。Java语言的线程状态在Thread的源码中,定义了一个枚举类State,在Java语言中把线程的6种状态写的很清楚:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED这里有个小调查,你查过这个类并阅读过它的注释?(欢迎留言和足迹)五响之歌在耳边响起,Java中的线程状态竟然比常见的5种线程状态多了1,变成了6。这看似复杂,但它不是你想的那样。Java有基于通用线程状态的剪裁和丰富。总的来说少了一个。再看图片,注意颜色区分。在Java语言中,runnable状态和一般线程状态的running状态合并为Runnable,休眠状态又细分为三种(BLOCKED/WAITING/TIMED_WAITING);反过来理解这句话,就是这三种状态在操作系统眼里都是休眠状态,不会得到CPU的使用权。见上图右侧【Java语言中的线程状态】。睡眠状态和休眠状态之间切换就可以了,写并发程序大多就是这两种状态之间的转换。所以我们需要了解什么时机会触发这些状态转换。远看轮廓,近看细节。我们用上面的Java语言对图进行细化,将触发的节点放到图中(这个看似复杂的图其实可以分解成三句话,别慌),我们看:RUNNABLE和BLOCKED状态转换如果且仅(justonly)一种情况会从RUNNABLE状态进入BLOCKED状态,即线程正在等待synchronized内置隐式锁;如果等待线程获取了synchronized内置隐式锁,也会从BLOCKED状态变为RUNNABLE状态注:上面提到,从操作系统的一般状态来看,调用阻塞API的线程会进入休眠状态(释放CPU的使用权),但是在JVM层面,Java线程的状态是不会改变的,也就是说,Java线程的状态还是会保持在RUNNABLE状态。JVM不关心操作系统调度的状态。从JVM的角度来看,等待CPU使用权(操作系统处于可执行状态)和等待I/O(操作系统处于休眠状态)都是在等待某种资源,所以都是分类为RUNNABLE状态——摘自《Java并发编程实战》RUNNABLE与WAITING状态转换调用不带时间参数的等待API会从RUNNABLE状态进入WAITING状态;被唤醒后,会从WAITING进入RUNNABLE状态。RUNNABLE和TIMED-WAITING状态转换调用带时间的参数等待API,自然会从RUNNABLE状态进入TIMED-WAITING状态;当它被唤醒或者超时时间到时,就会从TIMED_WAITING状态进入RUNNABLE状态。图中有相当多的转换API。其实不用着急,后续分析源码章节,自然就记住了,现在对状态转换的节点有了印象,知道了。相信到这里,你在看Java线程的生命周期时,眼睛就不会那么迷茫了。关键是RUNNABLE和休眠状态的切换。一目了然,如何查看线程中的状态,具体的代码触发点如何查看线程的状态。在程序中调用getState()方法。Thread类中也存在getState()方法,用于查看当前线程状态。这个方法是返回上面提到的枚举类StateNEW,是上面提到的编程语言特有的。通过继承Thread或者实现Runnable接口定义一个线程后,此时的状态为NEWThreadthread=newThread(()->{});System。out.println(thread.getState());RUNNABLE调用start()方法后,线程处于RUNNABLE状态Threadthread=newThread(()->{});thread.start();//Thread.sleep(1000);System.out.println(thread.getState());BLOCKED等待synchronized内置锁,会处于BLOCKED状态publicclassThreadStateTest{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt1=newThread(newDemoThreadB());Threadt2=newThread(newDemoThreadB());t1.start();t2.start();Thread.sleep(1000);System.out.println((t2.getState()));System.exit(0);}}classDemoThreadBimplementsRunnable{@Overridepublicvoidrun(){commonResource();}publicstaticsynchronizedvoidcommonResource(){while(true){}}}WAITING调用线程的join()等方法,由RUNNABLE变为等待状态publicstaticvoidmain(String[]args)throwsInterruptedException{Threadmain=Thread.currentThread();Threadthread2=newThread(()->{try{Thread.sleep(1000);}catch(InterruptedExceptione){Thread.currentThread()。interrupt();e.printStackTrace();}System.out.println(main.getState());});thread2.start();thread2.join();}TIMED-WAITING调用sleep(long)和其他methods,线程从RUNNABLE状态变为TIMED-WAITING状态publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread3=newThread(()->{try{Thread.sleep(3000);}catch(InterruptedExceptione){//为什么调用中断方法?Thread.currentThread().interrupt();e.printStackTrace();}});thread3.start();Thread.sleep(1000);System.out.println(thread3.getState());}TERMINATED线程执行完自然会进入TERMINATED状态Threadthread=newThread(()->{});thread.start();Thread.sleep(1000);System.out.println(thread.getState());以上就是在程序中查看线程,写个测试看看状态没问题,真正的程序怎么能让你加那么多没用的代码,所以,翠华,酸菜(jstack)jstack命令查看我相信你听说过这个东西,jstack这个命令比较强大,不仅可以查看线程当前的状态,还可以查看调用栈、锁等线程栈信息。你可以随意写一些程序,这里我使用上面的WAITING状态代码,修改睡眠时间Thread.sleep(100000),然后在终端按照下图依次执行以下命令本文将教大家如何使用jstack查看线程堆栈信息.Arthas是一个强大的工具。不用说,在线故障查找监控没有错。希望大家可以灵活使用这个工具,攻克疑难杂症,查看线程栈详情。很方便:https://alibaba.github.io/art...相信你已经跟Arthas确认过,即使线程生命周期的整体状态已经结束了,在写并发程序的时候多问问自己:调用一个API会将您的线程设置为“为什么”状态?多问自己几遍,自然就会记住上面这张图。灵魂问为什么会调用Thread.sleep,在catch异常后调用Thread.currentThread().interrupt();进入BLOCKED只有一种情况,就是等待synchronizedmonitor锁,然后调用JUC中的Lock.lock()方法,如果有线程在等待锁,那么线程的状态是什么?为什么?publicclassThreadStateTest{publicstaticvoidmain(String[]args)throwsInterruptedException{TestLocktestLock=newTestLock();Threadthread2=newThread(()->{testLock.myTestLock();},"thread2");Threadthread1=newThread(()->{testLock.myTestLock();},"thread1");thread1.start();Thread.sleep(1000);thread2。start();Thread.sleep(1000);System.out.println("****"+(thread2.getState()));Thread.sleep(20000);}}@Slf4jclassTestLock{privatefinalLocklock=newReentrantLock();publicvoidmyTestLock(){lock.lock();try{Thread.sleep(10000);log.info("testLockstatus");}catch(InterruptedExceptione){log.error(e.getMessage());}最后{lock.unlock();}}}3.synchronized和Lock有什么区别?参考感谢前辈总结的精华。我写的并发系列很多都参考了以下资料:Java并发编程实战Java并发编程代码之美,产出高效的Java并发编程艺术……我也在逐步总结常见的并发面试题(总结ing...)答案整理后会通知大家,请继续关注