本文从线程的生命周期及其对应的线程的5中状态入手,来详细讲解使线程从一个状态转变为另一种状态的常用操作方法,包括完成线程创建、线程启动、线程休眠、线程加入、线程让步、线程中断等操作需要使用的方法及其具体实现。
一、线程的生命周期
线程是具有生命周期的,其中包含5种状态:出生状态、就绪状态、运行状态、暂停状态(包括休眠状态、等待状态和阻塞状态)、死亡状态。
1、线程生命周期的5个状态
(1)出生状态
出生状态就是线程被创建时的状态,是指线程已经被创建但尚未被指定(start()尚未被调用)。
我们使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于出生状态。它保持这个状态直到程序 start() 这个线程。
(2)就绪状态
当线程对象调用了start()方法之后,该线程就进入就绪状态(也称为可执行状态)。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。这个状态下中的线程虽然不一定正在执行,但CPU时间随时可能被分配给该线程。
(3)运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
(4)阻塞状态
阻塞状态:是指线程没有被分配到CPU时间,无法执行。如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
(5)死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
正常情况下,run()方法返回使得线程死亡,这时候会产生异常。调用stop()或者destory()也有同样的效果,但是不推荐使用stop和destory,因为属于强制种植,不会释放锁。
2、线程生命周期状态图
也有一些资料将线程的声明周期划分为6个阶段:NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED,感兴趣的可自行查阅资料,我们就不在这里赘述了。本篇文章及后续文章中,本人还是统一将线程生命周期看作5个状态。
二、线程的操作
除了最基础的创建、启动线程,还有一些方法是使线程从一个状态转变为另一个状态的,包括包括线程的休眠、线程加入、线程让步、线程中断等操作。
1、创建线程
我们使用new关键字创建线程对象。创建线程的方式包括:通过Thread类重写run()方法实现;通过Runnable接口创建Thread对象并调用start()方法实现;通过Callable和Future创建线程三种方法。
使用new()方法创建线程对象,三种线程实现方法都会之间或间接的创建Thread对象。
new()
- 继承 Thread 类
- 实现 Runnable 接口
- 通过Callable和Future创建线程
创建线程的三种方法的详细内容请参阅多线程的3种实现方式部分内容。
2、启动线程
(1)start()方法启动线程
三种方法创建的线程对象,都与Thread对象相关联:Thread类实现线程过程中会直接创建Thread对象;通过Runnable接口实现线程需要使用参数为Runnable对象的构造方法创建Thread对象;Callable和Future创建线程过程中,会使用FeatureTask对象作为Thread对象的target创建线程。三种方式都会创建直接或间接的与Thread对象相关联的对象,因此都能够通过调用Thread的start()方法来启动线程。
.start()
- 调用 start() 方法
创建并启动线程的三种方法的详细内容请参阅多线程的3种实现方式部分内容。
(2)判断线程是否启动
使用isAlive()方法来判断线程是否已经启动而且仍然在启动
.isAlive()
(3)判断线程是否为启动状态
实例1:创建并启动线程,在线程启动前后调用isAlive()方法判断线程启动状态
*
实现Runnable接口的类
**/
//①首先需要创建一个实现Runnable接口的类,命名为RunnableDemo
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
//②在RunnableDemo中使用参数为Runnable对象的构造方法创建Thread类
public RunnableDemo(String name){
threadName = name;
System.out.println("创建"+threadName);
}
//③在实现类中重写Runnable接口的run方法
public void run() {
for (int i = 0; i < 5; i++) {
for (long k = 0; k < 100000000; k++) ;
System.out.println(threadName + ": " + i);
}
}
//④在实现类中重写Runnable接口的start()方法,并分配线程
public void start () {
System.out.println("启动 " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
/*
测试程序实现的类
**/
//④创建RunnableDemo实现的测试类ThreadTest
public class ThreadTest{
public static void main(String args[]) {
//⑤实例化RunnableDemo对象
RunnableDemo R1 = new RunnableDemo( "Thread_1");
RunnableDemo R2 = new RunnableDemo( "Thread_2");
//实例化Thread子类
Thread t1 = new Thread(R1);
Thread t2 = new Thread(R2);
//⑥调用重写的start()方法,启动线程
System.out.println("线程开始执行之前,线程isAlive?-->"+t1.isAlive()); //使用IsAlive()方法查看线程是否活着
t1.start();
System.out.println("线程开始执行之后,线程isAlive?-->"+t1.isAlive());
t2.start();
}
}
Console:
创建Thread_1
创建Thread_2
线程开始执行之前,线程isAlive?-->false
线程开始执行之后,线程isAlive?-->true
Thread_1: 0
Thread_2: 0
Thread_2: 1
Thread_1: 1
Thread_2: 2
Thread_1: 2
Thread_2: 3
Thread_1: 3
Thread_2: 4
Thread_1: 4
⚠️注意:主线程有可能比其他线程先执行完。
3、线程休眠
能控制线行为的方法之一就是调用sleep()方法让线程休眠,线程休眠指的是让线程暂缓执行,等到预计时间之后再恢复执行。
(1)线程休眠的操作方法
sleep()方法可以指定线程休眠时间,线程休眠的时间以毫秒为单位。
.sleep()
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
上述代码会使线程休眠2秒,从而在2秒内不进入到就绪状态。由于sleep()方法可能会抛出InterruptedException异常,所以将sleep()方法放在try…catch异常捕捉代码块中。虽然使用了sleep()方法的线程在一段时间后会醒来,但是并不能保证线程醒来后就能直接进入到运行状态,只能保证醒来的线程进入到就绪状态。
(2)线程休眠的逻辑
sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。执行逻辑可以总结为以下三步:
-
线程休眠会交出CPU,让CPU去执行其他的任务。
-
调用sleep()方法让线程进入休眠状态后,sleep()方法并不会释放锁,即当前线程持有某个对象锁时,即使调用sleep()方法其他线程也无法访问这个对象。
-
调用sleep()方法让线程从运行状态转换为阻塞状态;sleep()方法调用结束后,线程从阻塞状态转换为可执行状态。
(3)线程休眠的实现
实例2:线程执行次数是3的倍数时,线程休眠2000毫秒
创建线程,重写run()方法,使2个线程循环执行,当每个线程执行次数是3的倍数时,线程休眠2000毫秒。
class ThreadSleep extends Thread{
public ThreadSleep(String name){
super(name);
}
public synchronized void run(){
try{
for(int i = 0 ; i<5 ; i++){
//为3的倍数时,休眠2000毫秒
if(i%3 == 0){
Thread.sleep