前言:
线程生命周期和生命周期函数是面试中的重灾区之一。但是聊到生命周期,在博客上能搜到有人说是5种,有人说是7种,甚至有6种的说法,导致理解这个原本不算复杂的问题变得模糊。本文和大家分享一下我自己的心得,希望能帮助大家清晰的理解线程生命周期,及为何有各种博客对线程生命周期有不同的说法
1.线程模型,线程生命周期
1) 理解线程
有一句定义我认为特别经典:进程是承担分配系统资源的基本实体,线程是独立调度的基本单位。
从java-web开发程序员的角度看,一个前端请求进来服务器;一个异步方法调用;一个MQ的监听接到消息,用完完成这些的,都是一个线程
2)线程生命周期模型
我们在各个博客看到的4,5,6,7种生命周期状态的,其实是不同角度去定义线程生命周期。作为java开发,我认为,我们只需要两个角度来理解线程生命周期
1>操作系统角度,线程的生命周期模型
线程被新建,新建后进入就绪队列变成就绪态,然后等到CPU分配资源,即抢占到了CPU时间片后可开始执行,变成运行态,运行态中,可能由于各种因素,如IO之类导致运行的线程变成阻塞态,阻塞态的线程通过被唤醒,再次成为就绪态。当全部代码执行完毕,线程死亡。
2>Java角度线程的生命周期模型
java种,为了更好的控制线程和锁,将线程状态细分为了6种,在Thread.Statu种有定义,大家可以点进Thread看一下
其状态流转及函数调用如下图:
注意,Thread定义的状态中,对于运行态和就绪态,统一放在了RUNABLE种,所以java的RUNABLE实际上对应了操作系统的两种状态,分别是:
就绪态,运行态。
运行态可以通过调用yeid()变成就绪态,就绪态无法手动变成运行态
3>操作系统的线程状态和JAVA的线程状态对应关系
2.生命周期相关函数 wait(),notify(),notifyAll(),join(),sleep(),stop(),interrupt()
·Object.yeid():
暂时释放线程对CPU的占用,线程会由运行态从新回到就绪态再次等待分配到CPU资源
·Object.wait(),Object.notify(),Object.notifyAll():
Object.wait()用于挂起线程,notify,notifyAll则用于唤醒。这三个函数都依赖于对象的监视器(Monitor),对Monitor没有概念的同学可以看一下我的另外一篇分享
大致说一下minitor,每个对象 作为锁资源( 即synchronized(对象) ) 时候都会绑定一个Monitor对象,Monitor对象中有一个waitSet集合,可以粗略理解为下图结构:
而在线程中调用了Object.wait()线程就会被加入到这个Object对应的Monitor的waitSet中并变成阻塞态。这也就是为什么,obj.waitI()必须在synchronized(obj) 中调用。因为obj在被synchronized锁定后,才会绑定Monitor对象,而obj.waitI(),要将当前线程放入Monitor对象的waitSet集合
·Object.notify(),Object.notifyAll()
notify()是从waiSet中随机的取一个唤醒,而notifyAll()则是唤醒全部
·Thread.join()
先上一张代码图
图左侧代码中,主线程调用子线程对象join方法(注意,对象的!对象的!),这时,主线程会进入阻塞,等待子线程执行结束后继续执行。
了解join()用法后,分析一下join()原理,图的右侧是java中对Thread的实现,可以看到join()的底层还是调用了Object.wait(),我们之前讲wait()时候说了,wait()一定是基于某个锁资源对象的,所以,thread.join()方法是一个synchronize的方法。即thread本身就是一个锁资源,主线程调用 thread.join时候,相当于是进行了如下的代码:
主线程这时进入了thread对象对应的Monitro对象的waiSet中变成了阻塞状态。
了解以上之后,再思考一个问题,wait()/notify()成对出现才能阻塞/唤醒线程,thread.join底层既然是wait(),那么wait()后并没有进行notify(),主线程是如何醒过来的?
这里涉及到一个JVM对线程处理的知识点: 当线程执行线程thread执行完毕之后,JVM会调用lock.notify_all(thread);唤醒持有threadA这个对象锁的线程
所以,在子线程执行完成之后,主线程重新被唤醒继续执行。
·Thread.sleep(x)
了解sleep之前需要了解一个操作系统概念,叫做调度器,顾名思义,调度器是用来调度操作系统资源的,其功能就包括分配cpu资源给线程,让线程执行及挂起
而sleep的底层,其实就是向调度器发送了信号,由调度器来让出当前线程的cpu资源,并在指定时间后重新分配。
·Thread.stop() Thread.interrupt(), Thread.isinterrupt()
Thread.interrupt() 不需要记的很复杂,只需要记住两件事
1. 它不真的中断线程执行,只是给线程打一个标记,这个标记可以用isinterrupt()来判断.
2.如果是一个阻塞的线程调用它,会直接抛出InterruptedException
Thread.stop() 会真的停止线程,注意,停止线程时候,如果线程的代码块进行了 try catch finally ,finally的代码会执行
再说一下所谓的优雅停止线程,目前官方推荐是使用 interrupt(),isinterrupt() 完成,伪代码如下:
while(true) {
.......
if(isinterrupt()) {
释放资源,处理线程后事
}
}
但是,实际工作中,我们需要进行中断的线程并不一定是能用一个循环体来控制的,可能是一长串代码,在执行很耗时的IO操作或者运算操作,这种情况下我们需要根据产品需求,比如界面有个对任务的终止操作之类的,立刻中断线程执行,并释放线程所占有的资源。这种情况下其实我们还是选择用 stop,然后再 finally代码块中完成资源的释放。