线程生命周期
线程生命周期
通用生命周期
一般的线程生命周期大致分为五大部分,如图所示。
- 初始状态,从程序角度上来将已经创建线程,但是操作系统层面还没有创建线程,这是编程语言所特有的。
- 就绪状态,也叫可运行状态这时操作系统已经创建了线程,只需要获取时间片就可以运行。
- 运行状态,刚刚获取时间片就进入运行状态。
- 休眠状态,从运行状态由于阻塞读取或者不满足条件变量则进入休眠状态,等到条件变量满足就从休眠状态变为就绪状态,等待获取时间片即可执行。
- 终止状态,一般是线程执行完毕,或者发生异常结束线程。
那是不是所有的编程语言都是如此呢?其实不然不同语言会将这五大部分进行不同程度的合并和细分,如JAVA。
JAVA生命周期
那JAVA的生命周期又是如何的呢?其实JAVA分为六种状态,如下所示
- NEW 初始状态
- RUNNABLE 可运行状态/运行状态
- BLOCKED 阻塞状态
- WAITING 无限等待状态
- TIMED_WAITING 有限等待状态
- TERMINATED 终止状态
JAVA中将可运行状态和运行状态合称为RUNNABLE状态,因为JVM将线程调度交由操作系统负责,这两个状态只是对操作系统调度层面有用。
同时JAVA将通用的休眠状态进一步细化为BLOCKED、WAITING、TIMED_WAITING三个状态,所以JAVA生命周期最终如下所示。
状态的相互转化
NEW转RUNNABLE
JAVA 程序创建完线程就是NEW状态,这是编程语言所特有的状态,创建线程的方式有两种
通过继承Thread类,重写run方法实现
class Test extends Thread{
@Override
public void run() {
// 重写run方法逻辑
}
}
// 创建线程对象
Test test = new Test();
通过实现Runnabel接口,重写run方法
class Test1 implements Runnable{
@Override
public void run() {
// 重写run方法逻辑
}
}
// 创建线程对象
Test1 test = new Test1();
Thread thread = new Thread(test);
线程对象NEW出来后,线程创建成功线程状态为NEW,但此时线程不会被系统调用,因为这是在语言层面创建的线程,只有将线程转为RUNNABLE状态才能执行线程,怎么做呢?执行线程对象的start()方法即可。
RUNNABLE转BLOCKED
由RUNNABLE转BLOCKED,目前只有一种途径,那就是并发原语synchronized,当一个线程进入同步块后,其余线程只能等待,这个等待就是从RUNNABLE转为了BLOCKED,当等待线程获取隐式锁后状态自动从BLOCKED状态转为RUNNABLE状态。
如果线程调用阻塞式API(阻塞式读文件、读取网络数据)会不会由RUNNABLE状态转成BLOCKED状态呢?其实不会,JVM认为不论是等待CPU执行(操作系统处于可执行状态)还是等待I/O(操作系统处于休眠状态)其实都是为了获取某个资源,所以JVM并没有变化还是RUNNABLE状态,而我们的操作系统层面线程会变为休眠状态。
所以我们平常说的阻塞式API执行,线程会阻塞,这是操作系统阻塞,而JAVA线程却是RUNNABLE状态。
RUNNABLE转WAITING
由RUNNABLE转WAITING,可以有如下三种途径
- synchronized代码块中调用wait()方法。
- join()方法,如果线程A调用线程B的join方法,那么线程A进入WAITING状态,等待线程B执行完毕后,唤醒线程A,线程A由WAITING状态转为RUNNABLE状态。
- LockSupport.park()方法,并发包中的锁都是基于此方法实现,当调用此方法后当前线程阻塞,由RUNNABLE状态转为WAITING状态,当调用LockSupport.unpark(Thread thread)可以唤醒目标线程,从WAITING状态转换为RUNNABLE状态。
RUNNABLE转TIMED_WAITING
TIMED_WAITING状态和WAITING状态基本类似,只是加入了超时逻辑,其转换有五种途径。
- 调用带有超时参数的Thread.sleep(long millis)方法。
- 在synchronized同步块中调用wait(long timeout)方法。
- 调用带有超时参数的Thread.join(long millis)方法。
- 调用带有超时参数的LockSupport.parkNanos(long nanos)方法。
- 调用带有超时参数的LockSupport.parkUntil(long deadline)方法。
RUNNABLE转TERMINATED
RUNNABLE转TERMINATED 一般分为两种情况。
- 线程正常执行完线程的run()方法。
- 线程执行异常,导致线程提前终止。
如果用户想提前终止线程呢?应该怎么去做?
线程终止
目前有两种线程中断的方法如stop(),interrupt()方法,如果只要在程序的使用,一定是推荐interrupt方法的,因为它更加优雅,不是简单粗暴的停止。
stop方法是简单粗暴直接kill线程,不给线程任何机会,假如stop方法想要停止线程A,而线程A又是持有ReentrantLock锁,线程A被stop()后是不会调用unlock释放锁的,那么其它线程都是无法获取锁的,同样现状的还有suspend() 和 resume()方法,所以生产中不能使用stop方法。
而interrupt方法就很温柔,调用中断方法后只是通知该线程,并不会强制中断,线程有机会执行其它逻辑,那被中断的线程怎么收到中断通知的呢?
有两种方法,一种是异常通知,还有一种是主动检测。
异常通知有如下情况
- 当线程A的状态为WAITING或者TIMED_WAITING时,调用线程A的interrupt()方法,会将线程A的状态由WAITING或者TIMED_WAITING状态转为RUNNABE状态,同时抛出异常InterruptedException。
- 当线程A的状态为RUNNABLE时,如果线程阻塞在java.nio.channels.InterruptibleChannel(换句话说就是JAVA中的状态为RUNNABLE但是因为阻塞在I/O上,操作系统的状态为休眠状态,所以只能通过异常响应),如果其它线程调用线程A的interrupt()方法,就会抛出异常ClosedByInterruptException。如果线程阻塞在java.nio.channels.Selector(多路复用选择器,非阻塞nio),如果其它线程调用线程A的interrupt()方法,线程A将java.nio.channels.Selector直接返回。
主动检测
如果线程处于RUNNABLE状态,并且没有阻塞在某个I/O上,如中断计算圆周率程序(一直在计算)线程A,其它线程调用线程A的interrupt()方法,这时只能通过isInterrupted()主动调用,实现主动检测。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效