Java 线程的生命周期和状态(实践加深理解)

一、常规回答(八股文)

线程的生命周期一共分为有6个状态,在某个时刻中,一个线程只会处在6个状态中的其中一种。

第1:初始状态(NEW) 当前的线程已经被创建了出来,但是还没有通过调用start()方法来启用。

第2:运行状态(RUNNABLE) 当前的进程已经调用了start()方法,进入了运行的状态,或者正在等待CPU的调度。

第3:阻塞状态(BLOCKED) 当前的线程会暂停执行,等待某些事件的发生,比如等待锁的释放。

第4:等待状态(WAITING) 当前线程会暂停执行,等待其他线程的特定事件。
如果线程调用wait()方法导致暂停,则需其他线程调用notify方法唤醒当前线程;如果线程调用了其他线程的join()方法,则需等待join对应的线程执行完毕后,才能唤醒当前线程。

第5:计时等待状态(TIMED_WAITING) 线程暂停执行,但是在经历了一段时间后会自动被唤醒。如调用Thread.sleep()方法,一段时间内线程被阻塞。

第6:终止状态(TERMINATED) 当前线程的逻辑已经执行完毕,或者遇到了异常而终止,就会进入终止状态。

二、加深理解

加深理解面试题答案的最好方法,无非就是自己实践一次。

所以,为了能够检测到 Java 程序中各个线程的运行状态,这里使用了一个小工具:arthas-boot

2.1 arthas-boot 安装(可跳过)

官方文档:https://alibaba.github.io/arthas
下载连接:https://arthas.aliyun.com/arthas-boot.jar

arthas 实际上是 Alibaba 开源的 Java 诊断工具,它的特点是使用方便,功能强大

最近学习了 JVM 调优相关的内容,使用到了这个工具,所以这里才想到使用它来检测 Java 程序的线程状态信息(如下图),来更好的理解线程在什么时候会进入什么状态。

Java 线程的生命周期和状态(实践加深理解)_第1张图片
这里简单说一下使用步骤,如不想安装则可以跳过直接看:[点击跳转] 2.2 开始检验线程的状态

第1步, 下载 arthas-boot.jar 文件:https://arthas.aliyun.com/arthas-boot.jar

第2步, 在本地系统运行一个Java程序,比如在 IDEA 中写一个死循环并运行:

Java 线程的生命周期和状态(实践加深理解)_第2张图片

第3步, 打开cmd,进入文件所在目录,执行命令

java -jar arthas-boot.jar

Java 线程的生命周期和状态(实践加深理解)_第3张图片

随后选择需要挂载的 Java 程序,这里我们要监控的是 Test3 这个类,所以在控制台输入1并回车。

Java 线程的生命周期和状态(实践加深理解)_第4张图片

等到出现以下 arthas 的图案,就说明运行成功了。

Java 线程的生命周期和状态(实践加深理解)_第5张图片
输入 thread 命令,即可查看当前进程的所有运行的线程:

Java 线程的生命周期和状态(实践加深理解)_第6张图片

2.2 检测线程的状态

2.2.1 初始状态(NEW)

当一个线程被创建但还没有调用start()方法运行的时候,这时的线程状态应该是 NEW

这个状态下的线程用arthas工具获取不到,所以就只能用代码获取了。

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            while (true) {
                // 模拟线程在执行某些操作
            }
        });
        test1.setName("test1");
        System.out.printf("线程[%s]当前的状态是: %s\n", test1.getName(), test1.getState());
    }
}

运行结果:

在这里插入图片描述

2.2.2 运行状态(RUNNABLE)

当一个线程被创建了,且调用了start()方法后,该线程状态应为RUNNABLE

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            while (true) {
                // 模拟线程在执行某些操作
            }
        });
        test1.setName("test1");
        test1.start();
        doSomething();
    }
    
    /** 保持程序一直执行 */
    private static void doSomething() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

Java 线程的生命周期和状态(实践加深理解)_第7张图片

2.2.3 阻塞状态(BLOCKED)

这个状态下的线程会暂停当前的执行。通常情况下,一个线程在等待锁(锁已被其他线程占用)的时候,这个线程会进入阻塞状态。

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            enter();
        });
        Thread test2 = new Thread(() -> {
            enter();
        });
        /*
        	两个线程都尝试进入enter()方法,
        	但只有一个线程能够拿到 `synchronized` 锁并进入方法,
        	另外一个线程尝试进入方法但会被阻塞。
		*/
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
    
    private synchronized static void enter() {
        System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());
        for (long j = 0L; j < 50000000000L; j++) {
            
        }
        System.out.printf("线程[%s]退出了,释放了锁\n", Thread.currentThread().getName());
    }
}

运行结果:

# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作

(以上是目前为止已打印的结果)

此时,线程test1处于运行状态,而线程test2因为没有拿到锁(锁被test1占用),所以处在阻塞BLOCKED状态。

Java 线程的生命周期和状态(实践加深理解)_第8张图片

2.2.4 等待状态(WATING)

(1/2) 情况1:线程本身手动调用 wait() 方法

执行 wait() 方法的那个线程,在执行了该方法后,会释放锁,并进入等待状态WAITING(只有别的线程调用notifyAll()方法才能唤醒该线程)

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            enter();
        });
        Thread test2 = new Thread(() -> {
            enter();
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
    
    private synchronized static void enter() {
        System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());
        for (long j = 0L; j < 100000000000L; j++) {
            if (j == 30000000000L) {
                System.out.printf("线程[%s]执行到一半时,调用了wait()方法\n", Thread.currentThread().getName());
                try {
                    // 冷知识1:wait()方法必须通过同步监视器对象(monitor)来调用
                    // 冷知识2:静态的同步方法中,monitor默认是当前类的class对象,即Test3.class
                    // 所以这里必须通过"Test3.class"来调用wait()方法,否则会报错
                    Test3.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.printf("线程[%s]执行完毕\n", Thread.currentThread().getName());
    }
}

运行结果:

# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作
线程[test1]执行到一半时,调用了wait()方法
线程[test2]进来了,它将执行以下耗时的业务操作  # 此处说明了wait()方法会释放锁

(以上是目前为止已打印的结果)

此时,第一个线程(test1)在方法中执行了wait()后,进入了WAITING状态。

Java 线程的生命周期和状态(实践加深理解)_第9张图片

(2/2) 情况2:某线程调用其他线程的join()方法

测试程序:

public class Test3 {
    
    private static Thread test1;
    private static Thread test2;
    
    public static void main(String[] args) throws InterruptedException {
        test1 = new Thread(() -> {
            System.out.println("线程test1开始执行");
            for (long j = 0L; j < 100000000000L; j++) {
                if (j == 30000000000L) {
                    System.out.println("线程test1执行到一半时,调用了t2的join()方法");
                    try {
                        test2.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("线程test1执行完毕");
        });
        test2 = new Thread(() -> {
            System.out.println("线程test2开始执行");
            for (long j = 0L; j < 100000000000L; j++) {
                
            }
            System.out.println("线程test2执行完毕");
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
}

运行结果:

# 控制台打印信息:
线程test1开始执行
线程test2开始执行
线程test1执行到一半时,调用了test2的join()方法 

(以上是目前为止已打印的结果)

由于线程test1调用了test2.join(),因此线程test1会暂停执行,只有test2执行完毕后才被唤醒。
此时test1处在等待状态WAITING

Java 线程的生命周期和状态(实践加深理解)_第10张图片

2.2.5 计时等待状态(TIMED_WAITING)

(1/3) 情况1:线程本身手动调用 wait(time) 方法

和 2.2.4 等待状态(WAITING)情况1 其实是类似的,只是调用的方法由wait()变成了wait(time)

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            enter();
        });
        Thread test2 = new Thread(() -> {
            enter();
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
    
    private synchronized static void enter() {
        System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());
        for (long j = 0L; j < 100000000000L; j++) {
            if (j == 30000000000L) {
                System.out.printf("线程[%s]执行到一半时,调用了wait(time)方法\n", Thread.currentThread().getName());
                try {
                    // wait()方法必须通过同步监视器(monitor)来调用
                    // 冷知识:静态的同步方法中,monitor默认是当前类的class对象,即Test3.class
                    // 所以这里必须通过"Test3.class"来调用wait()方法,否则会报错
                    Test3.class.wait(10 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.printf("线程[%s]执行完毕\n", Thread.currentThread().getName());
    }
}

运行结果:

# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作
线程[test1]执行到一半时,调用了wait(time)方法
线程[test2]进来了,它将执行以下耗时的业务操作

(以上是目前为止已打印的结果)

此时test1线程进入了计时等待状态(TIMED_WAITING

Java 线程的生命周期和状态(实践加深理解)_第11张图片

(2/3) 情况2:某线程调用其他线程的join(time)方法

和 2.2.4 等待状态(WAITING)情况2 其实是类似的,只是调用的方法由join()变成了join(time)

测试程序:

public class Test3 {

    private static Thread test1;
    private static Thread test2;

    public static void main(String[] args) throws InterruptedException {
        test1 = new Thread(() -> {
            System.out.println("线程test1开始执行");
            for (long j = 0L; j < 100000000000L; j++) {
                if (j == 30000000000L) {
                    System.out.println("线程test1执行到一半时,调用了test2的join(time)方法");
                    try {
                        test2.join(10 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("线程test1执行完毕");
        });
        test2 = new Thread(() -> {
            System.out.println("线程test2开始执行");
            for (long j = 0L; j < 100000000000L; j++) {

            }
            System.out.println("线程test2执行完毕");
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
}

运行结果:

# 控制台打印信息:
线程test1开始执行
线程test2开始执行
线程test1执行到一半时,调用了test2的join(time)方法 

(以上是目前为止已打印的结果)

由于线程test1调用了test2.join(time)test1会处在计时等待状态TIMED_WAITING

Java 线程的生命周期和状态(实践加深理解)_第12张图片

(3/3) 情况3:某线程调用sleep(time)方法

线程调用Thread.sleep(time)方法后,也会进入计时等待状态(TIMED_WAITING

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            try {
                System.out.println("年轻人身体就是好,一进来工作时倒头就睡");
                Thread.sleep(100 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        test1.setName("test1");
        test1.start();
    }
}

运行结果:

# 控制台打印信息:
年轻人身体就是好,一进来工作时倒头就睡

Java 线程的生命周期和状态(实践加深理解)_第13张图片

2.2.6 终止状态(TERMINATED)

当一个线程逻辑已执行完毕,或出现了异常而终止的时候,此时线程状态应该是 TERMINATED

这个状态下的线程用arthas工具获取不到,所以就只能用代码获取了。

测试程序:

public class Test3 {
    
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            System.out.println("test1执行过程出现异常");
            int a = 1 / 0;
        });
        Thread test2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                int a = 666;
            }
            System.out.println("test2任务已执行完毕");
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
        Thread.sleep(1000);
        System.out.printf("线程[%s]的状态: %s\n", test1.getName(), test1.getState());
        System.out.printf("线程[%s]的状态: %s\n", test2.getName(), test2.getState());
    }
}

运行结果:

# 控制台打印信息:
test1执行过程出现异常
test2任务已执行完毕
Exception in thread "test1" java.lang.ArithmeticException: / by zero
	at draft.Test3.lambda$main$0(Test3.java:8)
	at java.lang.Thread.run(Thread.java:748)
线程[test1]的状态: TERMINATED
线程[test2]的状态: TERMINATED

你可能感兴趣的:(常见Java面试题,java,开发语言)