目录
一、线程生命周期
二、线程状态
三、主要方法
四、上下文切换
五、参考资料
一、线程生命周期
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。如下图所示。
| 生命周期 | 特点 |
| 新建(New) | new创建线程,但未start(),线程生命开始 |
| 就绪(Runnable) | 调用start()后,CPU为线程安排时间片来执行线程(线程准备随时执行) |
| 运行(Running) | CPU执行线程run()(注意:入口只有Runnable) |
| 阻塞(Blocked) | 执行run()过程中,遇到同步锁、线程wait、线程sleep、I/O调用等进入阻塞(等待池),完成(如:拿到锁)后,进入Runnable |
| 终止(Dead) | 正常执行完成或出现异常或超时设置等,使线程终止,退出run(),线程生命结束 |
生命周期转换:
Runnable → Running:线程得到CPU资源
Running → Runnable:线程主动调用yield()方法或进入Blocked
Running → Dead:线程run执行完毕或发生异常
二、线程状态
枚举类java.lang.Thread.State,定义了6个线程状态,如下表所示。
| 线程状态 | 描述 |
| NEW | 尚未启动状态:new创建线程,但未start() |
| RUNNABLE | 可运行线程状态:CPU为线程安排时间片,但是CPU正在处理 |
| BLOCKED | 阻塞状态:进入同步锁 1. 等待监视器锁,以便进入一个同步的块/方法; 2. 调用Object.wait 之后再次进入同步的块/方法; 3. 线程日志中通常显示为java.lang.Thread.State: BLOCKED (on object monitor) ; |
| WAITING | 无限期等待状态:等待另一个线程来唤醒该线程 1. 不带超时的Object.wait 方法,日志显示为 java.lang.Thread.State: WAITING (on object monitor); 2. 不带超时的Thread.join 方法; 3. LockSupport.park 方法,日志显示为 java.lang.Thread.State: WAITING (parking); |
| TIMED_WAITING | 限期等待状态:一定时间后线程由系统自动唤醒 1. Thread.sleep 方法 |
| TERMINATED | 终止状态:正常或异常终止 |
注意:
1. 上述状态都是JVM的状态,与操作系统线程状态无关。
2. WAITING状态下的on object monitor:线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。处于这种状态的线程不会被分配时间片,它们要等待被其他线程显示唤醒。
3. "阻塞状态"与"等待状态"区别:"阻塞状态"在等待着获取一个排它锁,这个事件将在另一个线程放弃这个锁的时候发生,在程序进入同步区域的时候,线程就会进入阻塞状态;而"等待状态"则是在等待一段时间或者唤醒动作发生。
三、主要方法
| 方法 | 作用 |
| sleep(long millis) sleep(long millis, int nanos) | 1. 睡眠线程,睡眠时间为毫秒或纳秒 2. 不释放锁,状态进入TIMED_WAITING |
| yield() | 1. 当前线程让出CPU资源 2. 不释放锁,状态进入RUNNABLE(一般不使用) |
| interrupt() | 调用方线程打上中断标记,实际线程并没有停止还活着 |
| boolean isInterrupted() | 调用方线程是否有中断标记,不清除中断标记 |
| boolean interrupted() | 调用方所在的当前线程是否有中断标记,并清除中断标记 |
| boolean isAlive() | 调用方线程是否还活着 |
| join() join(long millis) join(long millis, int nanos) | 等待或等待多久调用方线程终止,即:线程进入TERMINATED |
注意: interrupted()是调用方所在的当前线程是否有中断标记,并清除中断标记,如下测试代码。
@Test
public void testThreadInterrupt() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (int i = 0; i < 2; i++) {
a += 5;
System.out.println("i = " + i + ",a = " + a);
}
}
});
thread.start();
// 调用方线程打上中断标记
thread.interrupt();
System.out.println("调用方线程有中断标志:" + thread.isInterrupted());
System.out.println("调用方所在当前线程有中断标志:" + thread.interrupted());
Thread.currentThread().interrupt();
System.out.println("调用方所在当前线程有中断标志,第一次获取:" + thread.interrupted());
System.out.println("调用方所在当前线程有中断标志,第二次获取:" + thread.interrupted());
System.out.println("调用方线程是否活着:" + thread.isAlive());
}
调用方线程有中断标志:true 调用方所在当前线程有中断标志:false 调用方所在当前线程有中断标志,第一次获取:true 调用方所在当前线程有中断标志,第二次获取:false 调用方线程是否活着:true i = 0,a = 5 i = 1,a = 10
四、上下文切换
如下图所示。CPU运行每个任务前,CPU都需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好CPU寄存器和程序计数器(Program Counter,PC)。CPU寄存器是CPU内置的容量小、但速度极快的内存。程序计数器是用来存储CPU正在执行的指令位置、或者即将执行的下一条指令位置。两者都是CPU在运行任何任务前,必须的依赖环境,因此也被叫做CPU上下文。
CPU上下文切换是先把前一个任务的CPU上下文(CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。CPU通过时间片算法来循环执行任务。根据任务的不同分为:进程上下文切换、线程上下文切换以及中断上下文切换。
线程调用start()方法后,则进入RUNNABLE状态。CPU会给每个线程分配CPU时间片来执行线程。时间片是CPU分配给线程的处理时间,时间片一般为几十毫秒(ms),所以CPU通过不停地切换线程执行,感觉是多个线程同时执行。单核处理器也支持多线程代码。
上下文切换也会影响多线程的执行速度,所以多线程并发不一定快。创建线程和上下文切换都耗时。如单线程处理的软件:Redis、Nginx等。
五、参考资料
Thread之一:线程生命周期及六种状态 - duanxz - 博客园
线程的生命周期及五种基本状态_xiaosheng900523的博客-CSDN博客_线程的生命周期
JAVA面试题 线程的生命周期包括哪几个阶段? - Java蚂蚁 - 博客园
Thread类中interrupt()、interrupted()和isInterrupted()方法详解_LZing_的博客-CSDN博客_interrupt
CPU 上下文切换是什么意思?(上)_一亩三分地-CSDN博客_上下文切换是什么含义
CPU 上下文切换是什么意思?(下)_一亩三分地-CSDN博客



