面试官: 说一下线程的生命周期过程

简介: 面试官: 说一下线程的生命周期过程

前言

目前正在出一个Java多线程专题长期系列教程,从入门到进阶含源码解读, 篇幅会较多, 喜欢的话,给个关注❤️ ~


线程状态的转换

如题,这也是我们面试中常被问到的。我们先看一下系统中线程状态是如何转换的。之前我们在讲进程和线程的概述时提到过,线程可以被视为轻量级的进程。所以在系统调度过程中,他们的状态转换是一致的。


首先最开始是创建阶段 NEW, 下一个阶段是就绪阶段 Ready, 紧接着就是调度转为执行阶段 Running, 执行的这个过程中,可能会被打断又变为Ready阶段,还有可能一些IO阻塞事件有又变为等待阶段 Waiting, 这个阶段又转换为 Ready阶段,当退出时,会转化为结束阶段 Terminated。说的有点抽象,大家可以对照下图理解一下。

301fba6563994d7e9a6d72bb12bad65b_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg


操作系统线程主要有以下三个状态:

  • 就绪状态(ready):线程正在等待使用CPU,经调度程序调用之后可进入running状态。
  • 执行状态(running):线程正在使用CPU。
  • 等待状态(waiting): 线程经过等待事件的调用或者正在等待其他资源(如I/O)。


Java中的线程状态

我们可以通过Thread.State查看它的状态枚举,一共提供了6种状态

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
复制代码

下面,我们一个个看


NEW

当前状态下的线程表示还未启动,也就是没有执行start, 我们来验证一下,可以通过getState来获取线程的状态

public class ThreadStateTest {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello");
        });
        System.out.println(t.getState());
        t.start();
        System.out.println(t.getState());
    }
}
复制代码


实际输出:

NEW
RUNNABLE
hello
复制代码

我们发现在start调用前后,线程状态是不一致的,未启动下是NEW


RUNNABLE

可运行线程的线程状态,处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自操作系统的其他资源。这个状态其实包含了操作系统的两种状态,Ready和Running


BLOCKED

线程阻塞等待监视器锁的线程状态。这个怎么去理解呢❓ 打个比方,我们去银行办业务,首先我们要去取号排队,只有排在你前面的人业务办完了,下面才轮到你。下面我们用代码来验证一下,首先我们看一下没加锁的情况下的状态

public static void main(String[] args) throws InterruptedException {
        Bank bank = new Bank();
        Thread t = new Thread(() -> {
            try {
                bank.doSomething("a");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t1 = new Thread(() -> {
            try {
                bank.doSomething("b");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
        t1.start();
        Thread.sleep(1000);
        System.out.println("t----->" + t.getState());
        System.out.println("t1---->" + t1.getState());
        Thread.sleep(3000);
        System.out.println("t----->" + t.getState());
        System.out.println("t1---->" + t1.getState());
        Thread.sleep(3000);
        System.out.println("t----->" + t.getState());
        System.out.println("t1---->" + t1.getState());
    }
复制代码


class Bank {
    public void doSomething(String name) throws InterruptedException {
        System.out.println(name);
        Thread.sleep(3000);
    }
}
复制代码


输出:

b
a
t----->TIMED_WAITING
t1---->TIMED_WAITING
t----->TERMINATED
t1---->TERMINATED
t----->TERMINATED
t1---->TERMINATED
复制代码


发现几乎都是并发执行的,下面我们看下加下是怎么的状态❓

class Bank {
    public void doSomething(String name) throws InterruptedException {
        synchronized(this) {
            System.out.println(name);
            Thread.sleep(3000);
        }
    }
}
复制代码


这里使用了synchronized同步锁,意思是同一时刻只允许一个线程去执行,具体用法后边会给大家讲,可以先留个印象,看下实际输出:

a
t----->TIMED_WAITING
t1---->BLOCKED
b
t----->TERMINATED
t1---->TIMED_WAITING
t----->TERMINATED
t1---->TERMINATED
复制代码

我们可以很清楚的看到两个线程的状态转换


WAITING & TIMED_WAITING

WAITING处于等待状态的线程正在等待另一个线程执行特定操作。上面的例子,我们看到TIMED_WAITING,超时等待状态。线程等待一个具体的时间,时间到后会被自动唤醒,可以看到调用了Thread.sleep(), 那么还有哪些方法可以让线程处于超时等待状态呢❓


  • Object.wait(long timeout)
class Bank {
    public void doSomething(String name) throws InterruptedException {
        synchronized(this) {
            if("a".equals(name)) {
                this.wait(3000);
            }
            System.out.println(name);
        }
    }
}
复制代码


输出:

b
t----->TIMED_WAITING
t1---->TERMINATED
a
t----->TERMINATED
t1---->TERMINATED
t----->TERMINATED
t1---->TERMINATED
复制代码


通过输出可以发现,t线程处于超时等待


  • Thread.join(long millis):等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;
public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("b");
        });
        t1.start();
        t1.join(60);
        System.out.println(t1.getState());
    }
复制代码


输出:

TIMED_WAITING
b
复制代码

发现指定了60 ms但线程内部实际执行大于`6000 ms,所以t1```会处于超时等待。


  • LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;
public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            LockSupport.parkNanos(3000*6000*4000L);
            System.out.println("a");
        });
        t.start();
        Thread.sleep(3000);
        System.out.println(t.getState());
    }
复制代码


输出:

TIMED_WAITING
复制代码


  • LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;
public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            LockSupport.parkUntil(System.currentTimeMillis() + 10 * 1000L);
            System.out.println("a");
        });
        t.start();
        Thread.sleep(6000);
        System.out.println(t.getState());
    }
复制代码


注意这里传的时间,看一下输出:

TIMED_WAITING
a
复制代码

调用以下方法,会导致线程处于WAITING,这里就不给大家一一演示了,差别其实就是是否指定了超时时间,大家可以自己试一下,可以参考上边。


  • Object.wait没有超时
  • 没有超时的Thread.join
  • LockSupport.park


TERMINATED

终止状态。此时线程已执行完毕。这个很好理解,之前给大家演示过了


Java线程状态转换

通过上面的了解之后,我们总结一下线程状态的转换

  • 首先是NEW阶段
  • 调用start进入 Runable阶段
  • 如果期间调用 wait(),join(),park(),线程会进去WAITING阶段,被唤醒后进入Runable阶段,可以通过notify(),nofityAll(),unpark()方式唤醒
  • 如果期间调用sleep(time), wait(time), parkNanos(time), parkUntil(time),join(time),线程会进入TIME_WAITING,被唤醒后进入Runable阶段,可以通过notify(),nofityAll(),unpark()方式唤醒
  • 如果线程处于等待锁状态,此时线程会进去BLOCK阶段,成功拿到锁后进入Runable阶段
  • 最终运行结束会进入TERMIATED阶段,也就是结束阶段


结束语

关于notify的用法,我们下节给大家讲,此处先有个印象,因为这个地方涉及到线程之间的通信了。本篇内容到这里就结束了, 大家自己一定要多去理解,不要去背, 下期给大家讲讲线程之间如何进行通信~

相关文章
|
5天前
|
Oracle Java 关系型数据库
一次惨痛的面试:“网易提前批,我被虚拟线程问倒了”
【5月更文挑战第13天】一次惨痛的面试:“网易提前批,我被虚拟线程问倒了”
28 4
|
7天前
|
消息中间件 前端开发 Java
美团面试:如何实现线程任务编排?
线程任务编排指的是对多个线程任务按照一定的逻辑顺序或条件进行组织和安排,以实现协同工作、顺序执行或并行执行的一种机制。 ## 1.线程任务编排 VS 线程通讯 有同学可能会想:那线程的任务编排是不是问的就是线程间通讯啊? 线程间通讯我知道了,它的实现方式总共有以下几种方式: 1. Object 类下的 wait()、notify() 和 notifyAll() 方法; 2. Condition 类下的 await()、signal() 和 signalAll() 方法; 3. LockSupport 类下的 park() 和 unpark() 方法。 但是,**线程通讯和线程的任务编排是
|
8天前
|
消息中间件 监控 前端开发
面试官:核心线程数为0时,线程池如何执行?
线程池是 Java 中用于提升程序执行效率的主要手段,也是并发编程中的核心实现技术,并且它也被广泛的应用在日常项目的开发之中。那问题来了,如果把线程池中的核心线程数设置为 0 时,线程池是如何执行的? 要回答这个问题,我们首先要了解在正常情况下,线程池的执行流程,也就是说当有一个任务来了之后,线程池是如何运行的? ## 1.线程池的执行流程 正常情况下(核心线程数不为 0 的情况下)线程池的执行流程如下: 1. **判断核心线程数**:先判断当前工作线程数是否大于核心线程数,如果结果为 false,则新建线程并执行任务。 2. **判断任务队列**:如果大于核心线程数,则判断任务队列是否
26 1
面试官:核心线程数为0时,线程池如何执行?
|
8天前
|
存储 安全 Java
这些年背过的面试题——多线程篇
本文是技术人面试系列多线程篇,面试中关于多线程都需要了解哪些基础?一文带你详细了解,欢迎收藏!
|
8天前
|
安全 Java
面试官:线程调用2次start会怎样?我支支吾吾没答上来
面试官:线程调用2次start会怎样?我支支吾吾没答上来
14 1
|
8天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
48 1
|
8天前
|
监控 Java 测试技术
面试准备不充分,被Java守护线程干懵了,面试官主打一个东西没用但你得会
面试准备不充分,被Java守护线程干懵了,面试官主打一个东西没用但你得会
31 1
|
8天前
|
Java
Java面试挂在线程创建后续,不要再被八股文误导了!创建线程的方式只有1种
Java面试挂在线程创建后续,不要再被八股文误导了!创建线程的方式只有1种
27 1
|
6天前
|
Python
|
1天前
|
缓存 NoSQL Redis
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?-- Redis多线程
【5月更文挑战第21天】Redis启用多线程后,主线程负责接收事件和命令执行,IO线程处理读写数据。请求处理流程中,主线程接收客户端请求,IO线程读取并解析命令,主线程执行后写回响应。业界普遍认为,除非必要,否则不建议启用多线程模式,因单线程性能已能满足多数需求。公司实际场景中,启用多线程使QPS提升约50%,或选择使用Redis Cluster以提升性能和可用性。
6 0

相关实验场景

更多