• Java线程的生命周期,终止与复位


    Thread生命周期

    生命周期概述

    Java的线程状态描述放在Thread类里面的枚举类State中.总共包含了6中状态(从出生到死亡)。

    1. public enum State {
    2. /**
    3. * 尚未启动的线程的线程状态 (没有start)
    4. */
    5. NEW,
    6. /**
    7. * 可运行线程的线程状态,是可以运行的线程状态(并不是在运行)
    8. * 这个状态在Java虚拟机中进行,但它可能等待来自操作系统的其他资源,比如CPU。
    9. * 内部包含了两个状态 【RUNNING】,【READY】这两个状态是可以互相流转的
    10. * 调用了start后线程就处于 READY 状态 ,等待操作系统分配CPU时间片,分配后进入 RUNNING 状态。
    11. * 当调用 yield() 方法后,只是谦让的允许当前线程让出 CPU ,但是不一定让,由操作系统决定,如果让 * 了当前线程就会进入 READY 状态,等待系统分配CPU时间片再次进入 RUNNING 状态。
    12. */
    13. RUNNABLE,
    14. /**
    15. * 阻塞状态。
    16. * 线程阻塞,等待监视器锁的状态,获取监视器锁后会进入 RUNNABLE 状态
    17. * 当发生线程锁竞争状态下,没有获取到锁的线程会被挂起进入阻塞状态,比如synchronized锁。
    18. */
    19. BLOCKED,
    20. /**
    21. * 等待线程的线程状态
    22. * 线程调用以下方法会处于等待状态:Object.wait()不超时、Thread.join()不超时等方法
    23. * 一个处于等待状态的线程正在等待另一个线程执行特定动作,例如:
    24. * 一个线程调用了Object.wait()方法在一个对象上正在等待另一个线程调用Object.nofify()或者
    25. * Object.nofifyAll()方法开启那个对象
    26. * 一个调用了Thread.join()方法的线程正在等待指定线程终止
    27. */
    28. WAITING,
    29. /**
    30. * 具有指定等待时间的等待线程的线程状态,调用一下方法会处于这个状态: Object.wait() 超时、 * Thread.join()超时 Thread.sleep(long) 等方法
    31. */
    32. TIMED_WAITING,
    33. /**
    34. * 已终止线程的线程状态
    35. * 线程执行完毕或者发生异常终止执行
    36. */
    37. TERMINATED;
    38. }

    线程生命周期流程图

    线程生命周期测试

    1. public class ThreadStatusDemo {
    2. public static void main(String[] args) throws InterruptedException {
    3. // 测试 NEW RUNNABLE TERMINATED
    4. Thread terminated_thread = new Thread(() -> {
    5. long start = System.currentTimeMillis();
    6. // 运行三秒 ,打印TERMINATED_THREAD线程runnable状态
    7. while (System.currentTimeMillis()-start<3000){}
    8. }, "TERMINATED_THREAD");
    9. // NEW
    10. Thread.State state = terminated_thread.getState();
    11. System.out.println(terminated_thread.getName()+" :state = " + state);
    12. terminated_thread.start();
    13. TimeUnit.SECONDS.sleep(1);
    14. // RUNNABLE
    15. Thread.State state1 = terminated_thread.getState();
    16. System.out.println(terminated_thread.getName()+"state1 = " + state1);
    17. TimeUnit.SECONDS.sleep(5);
    18. Thread.State state2 = terminated_thread.getState();
    19. // TERMINATED
    20. System.out.println(terminated_thread.getName()+"state2 = " + state2);
    21. // RUNNABLE
    22. new Thread(() -> {
    23. while (true) {
    24. }
    25. }, "Runnle_Thread").start();
    26. // TIMED_WAITING
    27. new Thread(() -> {
    28. while (true) {
    29. try {
    30. TimeUnit.SECONDS.sleep(1);
    31. } catch (InterruptedException e) {
    32. e.printStackTrace();
    33. }
    34. }
    35. }, "Time_Waiting_Thread").start();
    36. // WAITING
    37. new Thread(() -> {
    38. while (true) {
    39. synchronized (ThreadStatusDemo.class) {
    40. try {
    41. ThreadStatusDemo.class.wait();
    42. } catch (InterruptedException e) {
    43. e.printStackTrace();
    44. }
    45. }
    46. }
    47. }, "Waiting_Thread").start();
    48. // 这两个看谁先抢占到cpu获得锁,另一个就blocked
    49. // timed_waiting
    50. new Thread(new BlockedDemo(), "Blocke01_Thread").start();
    51. // blocked
    52. new Thread(new BlockedDemo(), "Blocke02_Thread").start();
    53. }
    54. static class BlockedDemo extends Thread {
    55. @Override
    56. public void run() {
    57. synchronized (BlockedDemo.class) {
    58. while (true) {
    59. try {
    60. TimeUnit.SECONDS.sleep(100);
    61. } catch (InterruptedException e) {
    62. e.printStackTrace();
    63. }
    64. }
    65. }
    66. }
    67. }
    68. }

    启动线程

    java中的启动

    Java启动一个线程调用start方法,start方法内部调用了 start0()native方法。

    1. public synchronized void start() {
    2. . . .
    3. boolean started = false;
    4. try {
    5. // 调用native方法
    6. start0();
    7. started = true;
    8. } finally {
    9. try {
    10. if (!started) {
    11. group.threadStartFailed(this);
    12. }
    13. } catch (Throwable ignore) {
    14. /* do nothing. If start0 threw a Throwable then
    15. it will be passed up the call stack */
    16. }
    17. }
    18. }

    这个测试是为了验证上图的正确性,只贴了部分.

    Hotspot中的启动

    查看指引

    jvm.cpp找到JVM_StartThread方法。发现是先创建个 JavaThread作为本地线程然后启动这个本地线程(借助os【thread.cpp】,因为jvm是跨平台的,这里是以linux-os为示例)

    1. JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
    2. JVMWrapper("JVM_StartThread");
    3. JavaThread *native_thread = NULL;
    4. bool throw_illegal_thread_state = false;
    5. {
    6. MutexLocker mu(Threads_lock);
    7. if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
    8. throw_illegal_thread_state = true;
    9. } else {
    10. jlong size =
    11. java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
    12. size_t sz = size > 0 ? (size_t) size : 0;
    13. // 先创建一个JavaThread
    14. native_thread = new JavaThread(&thread_entry, sz);
    15. if (native_thread->osthread() != NULL) {
    16. native_thread->prepare(jthread);
    17. }
    18. }
    19. }
    20. if (throw_illegal_thread_state) {
    21. THROW(vmSymbols::java_lang_IllegalThreadStateException());
    22. }
    23. assert(native_thread != NULL, "Starting null thread?");
    24. if (native_thread->osthread() == NULL) {
    25. delete native_thread;
    26. if (JvmtiExport::should_post_resource_exhausted()) {
    27. JvmtiExport::post_resource_exhausted(
    28. JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
    29. "unable to create new native thread");
    30. }
    31. THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
    32. "unable to create new native thread");
    33. }
    34. // 然后启动这个本地线程 thread.cpp
    35. Thread::start(native_thread);
    36. JVM_END

    JavaThread 创建线程

    1. JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
    2. Thread()
    3. #if INCLUDE_ALL_GCS
    4. , _satb_mark_queue(&_satb_mark_queue_set),
    5. _dirty_card_queue(&_dirty_card_queue_set)
    6. #endif // INCLUDE_ALL_GCS
    7. {
    8. if (TraceThreadEvents) {
    9. tty->print_cr("creating thread %p", this);
    10. }
    11. initialize();
    12. _jni_attach_state = _not_attaching_via_jni;
    13. set_entry_point(entry_point);
    14. os::ThreadType thr_type = os::java_thread;
    15. thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
    16. os::java_thread;
    17. // 调用os(操作系统)创建个线程
    18. os::create_thread(this, thr_type, stack_sz);
    19. _safepoint_visible = false;
    20. . . .
    21. }

    thread.cpp 启动线程

    1. // tips: 启动线程的方法
    2. void Thread::start(Thread* thread) {
    3. trace("start", thread);
    4. // Start is different from resume in that its safety is guaranteed by context or
    5. // being called from a Java method synchronized on the Thread object.
    6. if (!DisableStartThread) {
    7. if (thread->is_Java_thread()) {
    8. // Initialize the thread state to RUNNABLE before starting this thread.
    9. // Can not set it after the thread started because we do not know the
    10. // exact thread state at that time. It could be in MONITOR_WAIT or
    11. // in SLEEPING or some other state.
    12. // tips:启动之后设置线程的状态为 可运行状态 RUNNABLE
    13. java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
    14. java_lang_Thread::RUNNABLE);
    15. }
    16. // 借助操作系统启动线程
    17. os::start_thread(thread);
    18. }
    19. }

    线程中断与复位

    不要使用stop方法

    线程的终止不要简单的调用 stop方法,这个方法和其他的线程控制方法(suspend,resume)一样都是过期了不建议使用的,这些方法都是不安全的。 例如stop()方法在结束一个线程的时候并不保证线程资源的正常释放,因此可能导致出现一些不确定的状态。 按照人类逻辑来理解:T1线程调用方法修改T2线程的状态,但是T2现在在做什么T1是不清楚的,所以强制他关闭就是不安全的,就好比在Linux中使用 kill -9 杀掉一个进程。

    使用interrupt方法

    interrupt()方法只是修改了被中断线程的中断标志 ,并没有做什么过分的事儿。就像平时写代码的时候修改某对象的标志,对象自己通过标志类决定执行什么逻辑。这里也是一样,interrupt()方法修改中断标志,被中断的线程,自己决定做什么事儿(中断或者不中断都是被中断线程自己决定的,外部只是通知他,不是强迫他)。追一下源码。

    1.Java调用interrupt方法

    2.通过指引找到 jvm.cpp#JVM_Interrupt方法

    1. thread.cpp interrupt 借用操作系统。直接通过系统调用 interrupt
    2. void Thread::interrupt(Thread* thread) {
    3. trace("interrupt", thread);
    4. debug_only(check_for_dangling_thread_pointer(thread);)
    5. // tips: 调用操作系统的interrupt方法
    6. os::interrupt(thread);
    7. }

    这里还是以os_linux.cpp为例最终调用osthread的set_interrupted修改状态

    这里就印证了上方的 Thread.interrupt()只是修改了线程的一个标志位 ,并没有做什么过分的事儿。

    线程的复位

    interruptedisInterrupted

    这两个放在一起是因为他们底层都是调用的同一个native方法isInterrupted()只是给了不同的入参。 再就是,有过面试官问到他两的区别,所以干脆放在一起。首先说结论 ,isInterrupted()会返回线程的中断状态,interrupted()不仅会返回中断状态,而且如果线程处于状态状态还会将线程终端状态复位(清除中断状态)。

    os_linux.cpp的is_interrupted()方法印证了上面说的isInterrupted()会返回线程的中断状态,interrupted()不仅会返回中断状态,而且如果线程处于状态状态还会将线程终端状态复位(清除中断状态)。

    其他的线程复位

    Java中只要抛出了InnterruptException异常的方法都对线程进行了复位。先理顺下为什么要这么做:查看下基本上抛出InnterruptException异常的方法都是线程阻塞方法,比如sleep(),wait(),join()。这类方法执行后线程会处于TIMED_WAITING或者WAITING状态,处于这类状态的线程是不受控的(线程丧失了对自己的主导,需要其他的线程唤醒,或者阻塞时间到达才能拥有自己的主导权),这个时候线程中断,线程自己却没办法处理。甚至可能永远等不到释放而无法执行中断。所以,在线程是中断状态下,执行方法让线程阻塞,就要抛出一个异常告诉外界 ,我现在是阻塞状态,并且将中断标记复位,方便外界进行处理(例如中断线程的执行或者继续阻塞方法),相当于给了外界一个改变线程状态的入口。 以sleep()为例追踪下源码:

    通过指引找到 jcm.cpp#JVM_Sleep

    方法入口就直接判断线程的中断状态了 ,is_interrupted()上面介绍过了,参数为true就是清除中断标志并且返回清除之前的中断状态。这里线程是中断状态的就直接抛出 InnterruptException sleep interrupted异常了。


  • 相关阅读:
    iOS开发中实现广告页的思路
    想加一个当信用分低于50分时
    day53--动态规划12
    新版AI系统ChatGPT源码支持GPT-4/支持AI绘画去授权
    统计和为 K 的子数组个数
    如何通过 6 种方法从 iPhone 恢复已删除的文件
    包含漏洞的str_replace函数绕过
    Java项目:SSM服装进销存管理系统
    【Jenkins使用】Jenkins 与 Git
    GPC规范--安全域基础概念
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/125614239