后端精进笔记02:线程状态与线程中止

199 阅读4分钟

一、线程状态

1.1 线程状态概述

线程一共有6种状态,由java.lang.Thread.State类定义

  • New:通过new创建线程而未启动的状态
  • Runnable:可运行状态,等待CPU调度
  • Blocked:
  • Waiting:非超时方式的线程等待状态(Object.wait、Thread.join、LockSupport.park等方式),需要由其他线程发通知来唤醒。
  • Timed Waiting:超时方式的线程等待状态(Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUtil),超时或由其他线程唤醒后,重新进入Runnable状态。
  • Terminated:线程终止状态,线程正常执行完成或遇到异常时将进入此状态。

1.2 线程状态转换

这6种状态的互相转化关系,如下图所示:

39

1.3 小试牛刀:在不同的情况下查看线程状态

当前案例将在main线程中读取各子线程的状态(Runnable状态将通过子线程自行查看)

1.3.1 源码

public class Demo02 {

    public static void main(String[] args) throws Exception {
        // 第一种状态切换 - 新建 -> 运行 -> 终止
        System.out.println("== 第一种状态切换  - 新建 -> 运行 -> 终止 ==");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1当前状态:" + Thread.currentThread().getState().toString());
                System.out.println("thread1 执行了");
            }
        });
        System.out.println("未启动线程,thread1当前状态:" + thread1.getState().toString());
        thread1.start();
        Thread.sleep(2000L); // 等待thread1执行结束,再看状态
        System.out.println("等待两秒,再看thread1当前状态:" + thread1.getState().toString());
        // thread1.start(); // 线程终止之后,再进行调用,会抛出IllegalThreadStateException异常

        System.out.println("== 第二种:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式) ==");
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {// 将线程2移动到等待状态,1500后自动唤醒
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread2当前状态:" + Thread.currentThread().getState().toString());
                System.out.println("thread2 执行了");
            }
        });
        System.out.println("未启动线程,thread2当前状态:" + thread2.getState().toString());
        thread2.start();
        System.out.println("启动线程,thread2当前状态:" + thread2.getState().toString());
        Thread.sleep(200L); // 等待200毫秒,再看状态
        System.out.println("等待200毫秒,再看thread2当前状态:" + thread2.getState().toString());
        Thread.sleep(3000L); // 再等待3秒,让thread2执行完毕,再看状态
        System.out.println("等待3秒,再看thread2当前状态:" + thread2.getState().toString());

        System.out.println();
        System.out.println("== 第三种:新建 -> 运行 -> 阻塞 -> 运行 -> 终止 ==");
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Demo02.class) {
                    System.out.println("thread3当前状态:" + Thread.currentThread().getState().toString());
                    System.out.println("thread3 执行了");
                }
            }
        });
      	// 主线程持有字节码锁后,启动thread3
        synchronized (Demo02.class) {
            System.out.println("未启动线程,thread3当前状态:" + thread3.getState().toString());
            thread3.start();
            System.out.println("启动线程,thread3当前状态:" + thread3.getState().toString());
            Thread.sleep(200L); // 等待200毫秒,再看状态
            System.out.println("等待200毫秒,再看thread3当前状态:" + thread3.getState().toString());
        }
        Thread.sleep(3000L); // 再等待3秒,让thread3执行完毕,再看状态
        System.out.println("等待3秒,让thread3抢到锁,再看thread3当前状态:" + thread3.getState().toString());

    }
}

执行结果:

== 第一种状态切换  - 新建 -> 运行 -> 终止 ==
未启动线程,thread1当前状态:NEW
thread1当前状态:RUNNABLE
thread1 执行了
等待两秒,再看thread1当前状态:TERMINATED

== 第二种:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式) ==
未启动线程,thread2当前状态:NEW
启动线程,thread2当前状态:RUNNABLE
等待200毫秒,再看thread2当前状态:TIMED_WAITING
thread2当前状态:RUNNABLE
thread2 执行了
等待3秒,再看thread2当前状态:TERMINATED

== 第三种:新建 -> 运行 -> 阻塞 -> 运行 -> 终止 ==
未启动线程,thread3当前状态:NEW
启动线程,thread3当前状态:RUNNABLE
等待200毫秒,再看thread3当前状态:BLOCKED
thread3当前状态:RUNNABLE
thread3 执行了
等待3秒,让thread3抢到锁,再看thread3当前状态:TERMINATED

二、线程中止

2.1 不正确的线程中止方式thread.stop()

以简单粗暴的方式中止目标线程,如果是一系列原子的操作,执行到一半,所在线程被调用了thread.stop()方法,将丢失原子性(执行到一半直接退出)

2.2 正确的线程中止方式thread.interrupt()

大多数情况下,只是改变目标线程的中断状态,需要由开发者自行获取该状态以判断目标线程是否中止,如果中止的话,需要执行哪些操作。

如果目标线程在调用wait()、join()或sleep()方法时被阻塞,则会抛出InterruptException异常。

如果目标线程是被IO或NIO的Channel阻塞,则IO操作会被中断或返回特殊异常值,以中止该线程。

2.3 小试牛刀:分别使用不正确、正确的方式中止线程

2.3.1 不正确的方式中止线程thread.stop()

public class Demo03 {
    
    static class StopThread extends Thread {
        private int i = 0, j = 0;

        @Override
        public void run() {
            synchronized (this) {
                // 增加同步锁,确保线程安全
                ++i;
                try {
                    // 休眠10秒,模拟耗时操作
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ++j;
            }
        }

        public void print() {
            System.out.println("i=" + i + " j=" + j);
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        StopThread thread = new StopThread();
        thread.start();
        // 休眠1秒,确保i变量自增成功
        Thread.sleep(1000);
        // 暂停线程
        thread.stop(); // 错误的终止
        while (thread.isAlive()) {
            // 确保线程已经终止
        } // 输出结果
        thread.print();
    }
}

​ 执行结果:i、j的数值不一致(可以看到丢失了操作的原子性)

i=1 j=0

2.3.2 正确的方式中止线程thread.interrupt()

……
public static void main(String[] args) throws InterruptedException {
    StopThread thread = new StopThread();
    thread.start();
    // 休眠1秒,确保i变量自增成功
    Thread.sleep(1000);
    // 暂停线程
    //  thread.stop(); // 错误的终止
    thread.interrupt(); // 正确终止
    while (thread.isAlive()) {
      // 确保线程已经终止
    } // 输出结果
    thread.print();
}
……

​ 执行结果:i、j的数值一致(可以看到保留了操作的原子性)

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at cn.zephyr.ch1.Demo03$StopThread.run(Demo03.java:21)
i=1 j=1