当前线程启动以后,不可能一直“霸占” CPU 独立运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
新建与就绪状态
当程序使用 new 关键字创建了一个线程之后,该程序处于新建状态,此时和其他的 Java 对象一样,仅仅由 Java 虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。
当线程对象调用 start() 方法之后,该线程处于就绪状态,Java 虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于 JVM 里线程调度器的调度。
启动线程使用 start() 方法,而不是 run() 方法!调用 start() 方法来启动线程,系统会把该 run() 方法当成线程执行体来处理;但如果直接调用线程对象的 run() 方法,则 run() 方法立即就会被执行,而且在 run() 方法返回其他线程无法并发执行 —— 也就是说,如果直接调用线程对象的 run() 方法,系统把线程对象当成一个普通对象,而 run() 方法也是一个普通方法,而不是线程执行体。且不能直接通过 getName() 获取当前执行线程的名字,而是需要使用 Thread.currentThread() 方法先获取当前线程,再调用线程对象的 getName() 方法获取线程的名字。
Thread源码本身的 run() 方法:
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
target参数在Thread构造函数中被赋值,如果执行的线程为继承的 Thread,则线程启动执行子类覆盖的 run() 方法体,如果是实现的 Runnable 接口,执行的则是实现类的 run() 方法;看代码:
public class FirstThread extends Thread {
private int i;
public FirstThread() {}
public FirstThread(Runnable r) {
super(r) ;
}
// 重写run方法,run方法的方法体就是线程执行体
public void run() {
super.run(); // 同时执行 Thread 的run() 方法才会执行 Runnable 接口实现类的run() 方法
for (; i < 5; i++) {
System.err.println("FirstThread:"+getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 2) {
new FirstThread().start();
new FirstThread(new SecondThread()).start(); // 如果子类run() 方法未有super.run(),则Runnable接口不会执行
new Thread(new SecondThread()).start();
}
}
}
}
具体内容可参考:Java 中的进程与线程
调用线程对象的 start() 方法之后,该线程立即进入就绪状态 —— 即“等待状态”,但该线程并未真正进入运行状态。只能对处于新建状态的线程调用 start() 方法,否则将引发 IllegalThreadStateException 异常。
运行和阻塞状态
- 就绪状态的线程获取CPU,执行run()方法的线程执行体,线程处于运行状态;
- 一个CPU在任何时刻只有一个线程处于运行状态
- 一个线程不可能一直处于运行状态(除非执行体很短),线程在运行期间可能会中断;
- 当一个线程中断,另一个线程获得执行机会,这种线程调度细节取决于底层平台所采用的策略;
- 抢占式策略:给每个线程分配小时间段处理任务,该时间段用完,系统就会剥夺线程所占用的资源,让其他线程获得执行机会;在选择下一个线程时,系统会考虑线程的优先级。
- 协作式调度策略:只有当一个线程调用了它的 sleep() 或 yield() 方法后才会放弃所占用的资源,即由线程主动放弃所占用的资源。
发生如下情况时,线程将会进入阻塞状态
:
- 1、线程调用 sleep() 方法主动放弃所占用的处理资源;
- 2、线程调用了一个阻塞式 IO 方法,在该方法返回之前,该线程被阻塞;
- 3、线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;
- 4、线程在等待某个通知(notify)
- 5、程序调用了线程的 suspend() 方法将该线程挂起
发生如下情况时,线程将会进入就绪状态
:
- 1、调度 sleep() 方法的线程经过了指定时间;
- 2、线程调用的阻塞式 IO 方法已经返回;
- 3、线程成功地获得了试图取得同步监视器;
- 4、线程正在等待某个通知时,其他线程发出了一个通知;
- 5、处于挂起状态的线程被调用了 resume() 恢复方法。
从图可知,线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。而就绪和运行状态之间的转换通常不受程序控制,而是由系统线程调度所决定,当处于就绪状态的线程获得处理器资源时,该线程进入运行状态;当处于运行状态的线程失去处理器资源时,该线程进入就绪状态。但有一个方法例外,调用 yield() 方法可以让运行状态的线程转入就绪状态。
线程死亡
线程会以如下三种方式结束,结束后就处于死亡状态
- run() 或 call() 方法执行完成,线程正常结束
- 线程抛出一个未捕捉的 Exception 或 Error
- 直接调用该线程的 stop() 方法来结束该线程 —— 该方法容易导致死锁,通常不推荐使用
当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受主线程的影响。
测试某个线程是否已经死亡,可以调用线程对象的 isAlive() 方法,当线程处于就绪、运行、阻塞三种状态时,该方法将返回 true;当线程处于新建、死亡两种状态时,该方法将返回 false。
Java中可以通过 interrupt()
中断线程,并通过 interrupted()
方法来判断线程是否已经中断,来结束线程 run() 执行体,从而终止当前线程。