线程的生命周期
0. 线程拥有的状态
线程可以有如下6种状态:
NEW
:新建RUNNABLE
:可运行BLOCKED
:阻塞WAITING
:等待TIMED_WAITING
:计时等待TERMINATED
:终止
线程的状态由枚举类 State
表示,可以通过 Thread
对象的 public State getState()
方法获得:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
1. 新建状态
当程序使用 new
关键字创建了一个线程之后,该线程就处于新建状态,此时这个线程还没有开始运行。
2. 就绪状态
当线程对象调用了 start
方法之后,该线程处于就绪状态,或称可运行状态。
一个可运行的线程可能正在运行也可能没有运行,要由操作系统为线程提供具体的运行时间。同时,一旦一个线程开始运行,它不一定始终保持运行,线程调度的细节依赖于操作系统提供的服务。就此,Java规范没有将正在运行作为一个单独的状态,一个正在运行的线程仍处于可运行状态。
Thread
对象的 start
方法与 run
方法的区别:
- 调用
start
方法来启动线程,系统会把该run
方法当成线程执行体来处理。 - 但若调用线程对象的
run
方法,则run
方法立即就会被执行,而且在run
方法返回之前其他线程无法并发执行。 - 直接调用
run
方法时,Thread
的this.getName()
方法返回的是该对象的名字,而不是当前线程的名字。
class InvokeRun extends Thread {
private int i;
public void run() {
for (; i < 100; ++i) {
// 直接调用run()方法时,Thread的this.getName()返回的是该对象的名字,而不是当前线程的名字
// 使用Thread.currentThread().getName()总是获取当前线程的名字
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; ++i) {
// 调用Thead的currentThread()方法获取当前线程
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
// 直接调用线程对象的run()方法
// 系统会把线程对象当成普通对象,把run()方法当成普通方法
// 所以下面两行代码并不会启动两个线程,而是依次执行两个run()方法
new InvokeRun().run();
new InvokeRun().run();
}
}
}
}
3. 阻塞和等待状态
当线程处于阻塞或等待状态时,它暂时是不活动的,它不运行任何代码,而且消耗最少的资源,要由线程调度器重新激活这个线程。具体细节取决于它是怎样达到非活动状态的。

线程从阻塞状态只能进入就绪状态,无法直接运行。
线程阻塞的情况可分为三种:
- 等待阻塞:运行的线程执行
wait()
方法,JVM 会把该线程放入等待池中。 - 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中。
- 其他阻塞:运行的线程执行
sleep()
或join()
方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当sleep()
状态超时、join()
等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
join 方法
Thread
提供了让一个线程等待另一个线程完成的方法,即 join()
方法。当在某个程序执行流中调用其他线程的 join()
方法时,调用线程将被阻塞,直到被调用 join()
方法的线程执行完为止。
注意,这句话不能表述为“当某个线程的
join()
方法被触发后,该线程将一直执行直至结束,这意味着所有其他线程将被阻塞”,想一想为什么?因为不存在一个线程永远处于运行态,观察一下枚举对象State
,它并不存在一个RUNNING
状态,只有RUNNABLE
。上述“调用线程将被阻塞”,描述的是调用线程与被调用线程这两者之间的关系,切不可上升到所有线程与被调用线程之间的关系,这透露的是两种不同的理念。
join()
方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。
join()
方法有如下三种重载形式:
方法 | 说明 |
---|---|
join() | 等待被 join 的线程执行完成 |
join(long millis) | 等待被 join 的线程的时间最长为 millis 毫秒,若届时被 join 的线程还未结束,则不再等待 |
join(long millis, int nanos) | 等待被 join 的线程的时间最长为 millis 毫秒加 nanos 毫微秒 |
由于计算机硬件、操作系统本身通常无法精确到毫微秒,因此一般很少使用第三种形式。
示例程序:
class JoinThread extends Thread {
// 提供一个有参数的构造器,用于设置该线程的名字
public JoinThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 100; ++i) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws Exception {
new JoinThread("新线程").start();
for (int i = 0; i < 100; ++i) {
if (i == 20) {
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
// main线程调用了jt线程的join()方法
// main线程必须等jt执行结束才会向下执行
jt.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
sleep 与 yield 方法
Thread
类的静态 sleep()
方法可以让当前正在执行的前线暂停一段时间,进入阻塞状态。
方法签名 | 说明 |
---|---|
static void sleep(long millis) | 让当前正在执行的线程暂停 millis 毫秒 |
static void sleep(long millis, int nanos) | 让当前正在执行的线程暂停 millis 毫秒加 nanos 毫微秒 |
Thread
还提供了一个与 sleep()
方法有点相似的静态方法 yield()
,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。
yield()
只是让当前线程暂停一下,让系统的线程调度器重新调度一次。完全可能的情况是:当某个线程调用了yield()
方法暂停之后,线程调度器又将其调度出来重新执行。- 当某个线程调用了
yield()
方法暂停之后,只有优先级与当前线程相同或更高、且处于就绪状态的线程才会获得执行的机会。
sleep
方法和 yield
方法的区别如下:
sleep
方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级,但yield
方法只会给优先级相同或更高的线程执行机会;sleep
方法会将线程转入阻塞状态,直到经过阻塞状态才会转入就绪状态,而yield
不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态,故而完全有可能某个线程被yield
方法暂停之后,立即再次获得处理器资源被执行;sleep
方法声明抛出了InterruptedException
异常,所以调用sleep
方法时要么捕捉该异常,要么显示抛出该异常,而yield
方法则没有声明抛出任何异常;sleep
方法比yield
方法有更好的可移值性,通常不建议使用yield
方法来控制并发线程的执行。
4. 终止状态
线程会以如下三种方式结束,结束后就处于终止/死亡状态:
run
方法执行完成,线程正常结束;- 线程抛出一个未捕获的异常终止了
run
方法,使线程意外终止; - 调用线程的
stop
方法来结束该线程。该方法抛出一个ThreadDeath
对象(Error
),从而杀死线程。stop
方法已废弃,不建议使用;- 如果一个线程正在操作共享数据段,操作过程没有完成就用
stop()
结束它的生命,将会导致数据操作的不完整,从这个角度出发也不提倡使用该方法。
为了测试某个线程是否已经死亡,可以调用线程对象的 isAlive()
方法,其返回一个 boolean
值:
线程状态 | isAlive() 返回值 |
---|---|
就绪(运行)/阻塞 | true |
新建/死亡 | false |
不要对处于死亡状态的线程调用 start()
方法,会引起 IllegalThreadStateException
异常,程序只能对新建状态的线程调用 start()
方法。
另外提一点,当主线程结束时,其他线程不会受任何影响,并不会随之结束。事实上,一旦子线程启动起来,它就拥有和主线程同样的地位。
interrupt 方法中断线程
除了已经废弃的 stop
方法,没有办法可以强制线程终止。不过,interrupt
方法可以用来请求终止一个线程。
当一个线程调用 interrupt
方法时,就会设置线程的中断状态。这是每个线程都有的 boolean
标志,每个线程都应该不时地检查这个标志,以判断线程是否被中断。
当在一个被 sleep
或 wait
调用阻塞的线程上调用 interrupt
方法时,那个阻塞调用(即sleep
或wait
调用)将被一个InterruptedException
异常中断。因此,如果线程被阻塞,就无法检查中断状态。
没有任何语言要求被中断的线程应当终止,中断一个线程只是要引起它的注意,被中断的线程可以决定如何响应中断。
Thread
类的相关方法:
void interrupt()
:向线程发送中断请求。static boolean interrupted()
:测试当前线程是否被中断。注意这是一个静态方法,这个调用有一个副作用——它将当前线程的中断状态重置为false
。boolean isInterrupted()
:测试线程是否被中断。static Thread currentThread
:返回表示当前正在执行的线程的Thread
对象。