引言


大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第四篇内容:线程状态。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!

在现代软件开发中,多线程编程已经成为提升应用程序性能和响应能力的关键技术。Java作为一门支持多线程编程的主流语言,其内置的丰富并发库使得开发者能够轻松创建、管理和协调多个线程以实现高效的并发执行。然而,深入理解和掌握Java线程的工作机制及其状态变化规律,是编写出稳定、高效并发程序的前提。

在Java中,一个线程在其生命周期内会经历一系列的状态变迁,从刚刚创建但尚未启动的新建状态(NEW),到正在运行或等待CPU时间片的就绪/运行状态(RUNNABLE),再到因争夺锁资源而暂时阻塞的BLOCKED状态,以及因调用等待方法进入等待其他线程唤醒的WAITING或TIMED_WAITING状态,直至线程执行完毕后的终止状态(TERMINATED)。这些状态的准确转换与管理对于理解线程的行为至关重要,也是排查诸如死锁、饥饿等问题的根本依据。

例如,在一个多线程环境下,当一个线程尝试获取已被其他线程持有的锁时,它将由RUNNABLE状态转变为BLOCKED状态,如以下代码片段所示:

synchronized (lock) {
    // 进入同步代码块前需获得锁,否则线程t2将会被阻塞
    Thread t1 = new Thread(() -> {
        // 持有锁并执行操作
    });
    t1.start();

    Thread t2 = new Thread(() -> {
        synchronized (lock) { // 线程t2试图获取已被t1持有的锁,因此变为BLOCKED状态
            // 执行相关操作
        }
    });
    t2.start();
}

同时,Java还提供了中断机制,通过Thread.interrupt()方法可以设置线程的中断标志位,而非直接强制停止线程,这就需要程序员在设计线程任务时关注如何正确响应中断请求,确保程序能在需要时优雅地关闭线程,如下所示:

Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 业务处理逻辑...
    }
    // 当线程收到中断信号后退出循环,并进行清理工作
});
thread.start();
// 在某个时刻决定中断线程
thread.interrupt();

因此,本篇博客将深入剖析Java线程的各种状态及其转化过程,结合具体的生活场景及代码示例,帮助读者建立起对Java线程状态全面而直观的认识,从而更好地驾驭多线程编程,提高并发程序的质量与可维护性。

操作系统线程状态


在现代操作系统中,线程被视为轻量级进程,它们的状态与进程状态有着紧密的对应关系。操作系统中的线程主要有三个基本状态:就绪状态、执行状态和等待状态。

  1. 「就绪状态(ready)」
    在这个状态下,线程已经准备就绪,具备了运行条件,等待操作系统的CPU调度器为其分配处理器资源。一旦获得CPU时间片,线程便能立即进入执行状态。在Java虚拟机(JVM)内部,这一状态被合并到RUNNABLE状态中,意味着一个Java线程在JVM层面可能是就绪态或者正在执行。

  2. 「执行状态(running)」
    当线程获得CPU并开始执行其任务时,它处于执行状态。在这个状态下,线程会占用CPU进行计算、读写内存等操作。对于Java线程而言,其RUNNABLE状态同样包括了线程实际在执行的过程。

  3. 「等待状态(waiting)」
    线程由于等待特定事件的发生或等待系统资源(如I/O完成)而暂时放弃CPU使用权,进入等待状态。例如,在Java中调用Object.wait()方法后,线程将释放持有的锁并进入WAITING状态,直到其他线程通过notify()或notifyAll()将其唤醒:

    synchronized (obj) {
        obj.wait(); // 线程在此处进入等待状态
    }

    另外,当线程因无法获取所需资源(如互斥锁)而暂停执行时,它也会进入BLOCKED状态,这在多线程同步场景中很常见:

    synchronized (lock) {
        // 若lock已被其他线程持有,则新尝试获取该锁的线程会进入BLOCKED状态
    }

综合来看,操作系统线程的状态转换是动态且频繁发生的,由操作系统内核的调度策略决定。而在Java编程中,虽然直接映射的是Java线程的六种状态,但其背后仍遵循操作系统线程的基本状态转换逻辑,并为开发者提供了更为细致的控制手段来管理线程生命周期。

Java线程的六种状态详解


  1. 「NEW状态」
    当创建一个Thread对象但尚未调用其start()方法时,线程处于NEW状态。在这个状态下,线程并未启动,仅完成了初始化阶段。例如:

    Thread thread = new Thread(() -> {
        // 任务代码
    });
    System.out.println(thread.getState()); // 输出 NEW

    一旦调用了start()方法,线程的状态将发生改变,开始执行线程体内的代码。值得注意的是,同一个线程不能重复调用start()方法,否则会抛出IllegalThreadStateException异常。

  2. 「RUNNABLE状态」
    RUNNABLE是Java中较为特殊的一个状态,它涵盖了传统操作系统中的就绪和运行两种状态。当线程已启动且CPU调度器为其分配了时间片或线程正在等待系统资源(如I/O操作)时,线程都处于RUNNABLE状态。在Java虚拟机(JVM)中,这样的线程既可能实际在执行,也可能随时准备执行。

  3. 「BLOCKED状态」
    BLOCKED状态表示线程因尝试获取锁而被阻塞,暂时无法继续执行。以下是一个模拟线程争夺锁从而进入BLOCKED状态的例子:

    Object lock = new Object();

    Thread t1 = new Thread(() -> {
        synchronized (lock) {
            try {
                Thread.sleep(1000); // 持有锁并休眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    Thread t2 = new Thread(() -> {
        synchronized (lock) { // 尝试获取已被t1持有的锁,因此进入BLOCKED状态
            // 执行相关操作
        }
    });

    t1.start();
    t2.start();

  4. 「WAITING状态」
    当线程调用Object.wait()、Thread.join()或者LockSupport.park()等方法后,主动放弃当前持有的锁并进入WAITING状态,此时线程必须由其他线程通过notify()、notifyAll()或LockSupport.unpark()方法唤醒才能恢复到RUNNABLE状态。
    举例来说,假设两个线程间的同步与唤醒过程如下:

    Object monitor = new Object();

    Thread waiter = new Thread(() -> {
        synchronized (monitor) {
            try {
                monitor.wait(); // 线程进入WAITING状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    Thread notifier = new Thread(() -> {
        synchronized (monitor) {
            // 做一些操作...
            monitor.notify(); // 唤醒waiter线程
        }
    });

    waiter.start();
    notifier.start();

  5. 「TIMED_WAITING状态」
    TIMED_WAITING状态与WAITING状态相似,区别在于线程会在指定的时间间隔后自动唤醒,无需其他线程显式地唤醒它。常见的情况包括使用Thread.sleep(long millis)、Object.wait(long timeout)、Thread.join(long millis)或LockSupport类的相关超时方法。例如:

    Thread t = new Thread(() -> {
        try {
            Thread.sleep(2000); // 线程进入TIMED_WAITING状态,2秒后自动唤醒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    t.start();

  6. 「TERMINATED状态」
    当线程正常结束执行,或者因为异常导致线程终止时,线程就会转为TERMINATED状态。在Java程序中,可以通过调用Thread.join()方法来等待一个线程完成执行,并观察其最终状态:

    Thread task = new Thread(() -> {
        // 任务代码
    });

    task.start();
    task.join(); // 等待task线程执行完毕
    System.out.println(task.getState()); // 输出 TERMINATED

    综上所述,Java线程的这六种状态体现了线程生命周期的完整过程,理解这些状态转换对于编写高效的并发程序至关重要。

    Java线程状态之间的转换过程

    1. 「BLOCKED与RUNNABLE状态间的转换」
      在多线程并发环境下,当一个线程尝试获取已经被其他线程持有的锁时,它将从RUNNABLE状态转为BLOCKED状态。例如,在Java的synchronized关键字同步块中,线程竞争锁资源的情况如下:

      Object lock = new Object();

      Thread threadA = new Thread(() -> {
          synchronized (lock) {
              try {
                  Thread.sleep(5000); // 持有锁并休眠
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      });

      Thread threadB = new Thread(() -> {
          synchronized (lock) { // 尝试获取已被threadA持有的锁,因此变为BLOCKED状态
              // 执行相关操作
          }
      });

      threadA.start();
      threadB.start();

      // 经过一段时间后,打印线程状态
      while (true) {
          if (threadB.getState() != Thread.State.BLOCKED)
              continue;
          System.out.println("Thread B is now BLOCKED");
          break;
      }

      当线程A释放了对锁的控制权后,线程B会重新变为RUNNABLE状态,并有机会获得CPU时间片执行其代码。

    2. 「WAITING状态与RUNNABLE状态的转换」
      线程调用Object.wait()方法或Thread.join()方法会进入WAITING状态,等待被其他线程唤醒。如以下例子所示,线程A在等待线程B结束后才继续执行:

      Thread threadB = new Thread(() -> {
          try {
              Thread.sleep(3000); // 线程B执行一段耗时操作
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      });

      Thread threadA = new Thread(() -> {
          try {
              threadB.join(); // 线程A等待线程B结束,此时线程A为WAITING状态
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("Thread A resumed after thread B finished.");
      });

      threadB.start();
      threadA.start();

      当线程B运行完毕后,线程A将由WAITING状态返回到RUNNABLE状态,进而得以执行。

    3. 「TIMED_WAITING与RUNNABLE状态的转换」
      通过调用Thread.sleep(long)、Object.wait(long)、Thread.join(long)等方法,线程可以设定一个超时时间后自动醒来,从而进入TIMED_WAITING状态。当超时时间到达或者提前被其他线程唤醒时,线程会回到RUNNABLE状态。

      Thread threadA = new Thread(() -> {
          try {
              Thread.sleep(2000); // 线程A进入TIMED_WAITING状态,等待2秒
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("Thread A back to RUNNABLE state after timeout.");
      });

      threadA.start();

    4. 「线程中断状态及其处理」
      Java提供了线程中断机制,允许线程在运行过程中响应中断请求。当调用Thread.interrupt()方法时,线程不会立即停止执行,而是设置其内部的中断标志位。线程可以通过检查自身的中断状态来决定如何响应中断请求。

      Thread threadC = new Thread(() -> {
          while (!Thread.currentThread().isInterrupted()) {
              // 执行任务...
          }
          System.out.println("Thread C interrupted and exiting gracefully.");
      });

      threadC.start();
      // 在某个时刻决定中断线程C
      threadC.interrupt();

      调用Thread.interrupted()会清除当前线程的中断状态并返回当前是否处于中断状态;而Thread.isInterrupted()仅检查当前线程的中断状态而不改变它。在实际应用中,开发者需要设计合理的中断策略以确保线程能够正确地处理中断请求并在适当的时候退出执行。

      注意事项


      在实践中,应当遵循以下几点建议:

      1. 「线程中断处理」:对可中断的任务,务必检查并响应中断请求,例如在循环体内部调用Thread.currentThread().isInterrupted()来判断并优雅地结束线程执行。
      while (!Thread.currentThread().isInterrupted()) {
          // 执行任务...
      }

      1. 「资源协调」:充分了解并掌握线程间如何通过锁、条件变量等手段进行有效的通信和协调,防止长时间的不必要等待造成系统瓶颈。
      2. 「异常处理」:在多线程环境中,任何线程抛出未捕获的异常都会导致该线程直接转为TERMINATED状态,因此要在关键代码段添加合适的异常处理逻辑。
      3. 「测试验证」:通过编写单元测试和集成测试,模拟不同场景下的线程状态转换,确保线程在各种情况下都能按预期流转,有效避免并发问题。

      总结

      总之,理解并熟练运用Java线程的状态转换原理是提升并发编程能力的关键所在,通过对线程状态的精细控制,可以打造出更健壮、高效的并发应用程序。

      通过深入剖析Java线程的六种状态及其转换过程,我们理解到在多线程编程中,合理管理线程状态对于保证程序正确执行、避免死锁和资源浪费至关重要。针对每个状态:

      • 「NEW」:创建线程后应尽快调用start()方法启动线程,避免出现未初始化的线程实例。
      • 「RUNNABLE」:虽然此状态表示线程可运行或正在运行,但要注意线程间的同步问题,使用synchronized关键字和Lock机制时,可能会导致线程进入BLOCKED状态。
      • 「BLOCKED」:对共享资源进行同步访问时,需要确保适时释放锁以允许其他等待线程继续执行,防止因竞争激烈而导致系统性能下降。
      • 「WAITING/TIMED_WAITING」:在设计线程间协作时,应适当利用Object类的wait/notify以及Thread.join等方法,明确设置超时时间,以便线程在合适时机唤醒或者自动返回RUNNABLE状态。
      • 「TERMINATED」:线程完成任务后应及时处理终止逻辑,如清理资源,并考虑是否需要重新启动或替换新的工作线程。

本文使用 markdown.com.cn 排版

深入浅出Java多线程(四):线程状态的更多相关文章

  1. java多线程--3 线程状态、线程方法、线程类型

    java多线程--3 线程状态.线程方法.线程类型 线程状态 创建状态: **就绪状态: ** 进入状态: 创建状态:启动线程 阻塞状态:阻塞解除 运行状态:释放CPU资源 阻塞状态: 进入状态: 运 ...

  2. 【Java多线程】线程状态、线程池状态

    线程状态: 线程共包括以下5种状态.1. 新建状态(New) 线程对象被创建后,就进入了新建状态.例如,Thread thread = new Thread().2. 就绪状态(Runnable) 也 ...

  3. Java多线程及线程状态转换

    以下内容整理自:http://blog.csdn.net/wtyvhreal/article/details/44176369 线程:是指进程中的一个执行流程.  线程与进程的区别:每个进程都需要操作 ...

  4. Java多线程(四) —— 线程并发库之Atomic

    一.从原子操作开始 从相对简单的Atomic入手(java.util.concurrent是基于Queue的并发包,而Queue,很多情况下使用到了Atomic操作,因此首先从这里开始). 很多情况下 ...

  5. Java多线程(四) 线程池

    一个优秀的软件不会随意的创建.销毁线程,因为创建和销毁线程需要耗费大量的CPU时间以及需要和内存做出大量的交互.因此JDK5提出了使用线程池,让程序员把更多的精力放在业务逻辑上面,弱化对线程的开闭管理 ...

  6. Java多线程之线程状态转换图

    说明:线程共包括以下5种状态.1. 新建状态(New)         : 线程对象被创建后,就进入了新建状态.例如,Thread thread = new Thread().2. 就绪状态(Runn ...

  7. Java多线程之线程状态总结

    概述 线程大家肯定不陌生,对于线程中的运行状态,自己经常搞混淆,这边按照下图记录下: 线程一般来说有如下几种状态: 新建,可运行,超时阻塞,等待阻塞,同步阻塞,死亡 yeild:当线程执行了yield ...

  8. 深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) [转载]

    本系列文章导航 深入浅出Java多线程(1)-方法 join 深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) 深入浅出多线程(3)-Future异步模式以及在JDK1.5Concu ...

  9. Java多线程之线程的状态迁移

    Java多线程之线程的状态迁移 下图整理了线程的状态迁移.图中的线程状态(Thread.Stat 中定义的Enum 名)NEW.RUNNABLE .TERMINATED.WAITING.TIMED_W ...

  10. Java 多线程:线程池

    Java 多线程:线程池 作者:Grey 原文地址: 博客园:Java 多线程:线程池 CSDN:Java 多线程:线程池 工作原理 线程池内部是通过队列结合线程实现的,当我们利用线程池执行任务时: ...

随机推荐

  1. POJ 1011 Sticks​ (DFS + 剪枝)

    题目地址:http://poj.org/problem?id=1011 题目大意 给出n个小木棒,组合成若干长度最短棍子 解题思路 首先将木棒从大到小排序 dfs(k, l), k是还剩多少木棒没用, ...

  2. VScode 中 Code Runner 插件乱码问题

    安装好,Code Runner 插件,可以很方便的运行多种语言的文本,但是提示和输出经常会报错 进入 File - > Preference -> setting, 然后在输入框搜索 se ...

  3. Codeforces Round #700 (Div. 2) A ~ D1个人题解

    Codeforces Round #700 (Div. 2) 比赛链接: Click Here 1480A. Yet Another String Game 因为Alice是要追求小,Bob追求大值, ...

  4. 供应链安全情报 | cURL最新远程堆溢出漏洞复现与修复建议

    漏洞概述 cURL 是一个支持多种网络协议的开源项目,被广泛集成到自动化构建.网络测试.网络数据采集以及其他网络相关的任务中,备受开发者和系统管理员青睐. cURL在2023年10月11日下午紧急发布 ...

  5. nextTick用法

  6. ACP 知识点总结

    记录下学习ACP过程不断遇到的且需要记录的知识点: 在阿里云专有网络VPC创建之后,路由器也是随着VPC一起自动创建,所以不需要手动创建,这个时候需要继续创建交换机才能在交换机种创建其他云产品. 7层 ...

  7. Go-性能测试-benchmark

  8. .NET技术面试题系列(2) -sql server数据库优化规范

    1.数据库优化规范 a.索引 每个表格都要求建立主键,主键上不一定需要强制建立聚集索引. 聚集索引,表中存储的数据按照索引的顺序存储,即逻辑顺序决定了表中相应行的物理顺序,因此聚集索引的字段值应是不会 ...

  9. [转帖]解Bug之路-记一次JVM堆外内存泄露Bug的查找

    https://zhuanlan.zhihu.com/p/245401095 解Bug之路-记一次JVM堆外内存泄露Bug的查找 前言 JVM的堆外内存泄露的定位一直是个比较棘手的问题.此次的Bug查 ...

  10. [转帖]AF_UNIX和AF_INET

    https://www.cnblogs.com/shangerzhong/p/9153737.html family参数代表地址家族,比较常用的为AF_INET或AF_UNIX.AF_UNIX用于同一 ...