一文解说Java的线程生命周期状态以及常用方法调用之后的线程状态

简介: 一般操作系统的线程状态都有哪些?Java中的线程周期状态的生命周期状态都有哪些?Java中线程状态是如何转化的?Java中线程常用方法有哪些?

本文的主要围绕着下面这个问题展开的,在阅读之前可以先自己思考一下问题的答案是什么?

  • 一般操作系统的线程状态都有哪些?
  • Java中的线程周期状态的生命周期状态都有哪些?
  • Java中线程状态是如何转化的?
  • Java中线程常用方法有哪些?

操作系统的线程状态

从操作系统的层面来说线程的状态划分为五种:初始状态、就绪状态、运行状态、阻塞状态和终止状态

初始状态(创建)

通过线程创建函数创建出来的新线程,在线程创建函数执行完后,将返回一个线程标识符供以后使用

就绪状态

操作系统中的线程被创建,可以分配CPU资源执行,但是还没有开始执行

运行状态

可运行状态的线程获得CPU资源以后,线程正在执行中,进入此状态

阻塞状态

指线程在执行中因某事件而受阻,处于暂停执行时的状态。此时线程会释放CPU资源,阻塞状态的线程没有机会获得CPU的使用权。

终止状态

线程执行完毕或者出现错误进入终止状态

iShot_2022-05-20_00.15.25.png

Java中的线程周期状态

参考JDK18中java.lang.Thread.State,Java线程的状态可以划分一下几种:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED

New(新建)

只是新创建出来的线程,还没有调用start()方法开始执行线程中的代码。

RUNNABLE(可运行)

这里的可运行状态(RUNNABLE)相当于操作系统线程状态中的就绪状态(READY)和运行状态(RUNNING)

抢占式调度系统会分配给就绪状态的线程一个时间片来执行任务。当时间片用完时,操作系统会根据优先级选择其他线程运行。一个线程在系统层面上可能是等待运行,也可能是正在运行。但是这些状态对JVM来讲,都可以看做可运行状态。

BLOCKED(阻塞)

该状态只与synchronized锁相关,WAITING,TIMED_WAITING状态下唤醒后因为需要竞争锁也会进入该状态

Java中的BLOCKED状态与操作系统中的阻塞状态不同,Java中的阻塞一定跟锁有关系。

从操作系统来说,线程因为调用阻塞API(如IO操作)会进入阻塞状态,在JVM下这个线程会是什么状态呢?不知道有没有大佬解释一下。

查询相关资料解释如下:

对JVM来说,等待CPU使用权(操作系统中线程处于可执行状态)和等待IO操作(操作系统中的线程处于休眠状态)没有区别,都是在等待某个资源,都被JVM认为是RUNNABLE状态。

所以是RUNNABLE状态?

WAITING(等待)

一个线程正在无期限等待另一个线程执行一个特定的动作唤醒此线程,被唤醒的线程会进入BLOCKED状态,重新竞争锁。

TIMED_WAITING(计时等待)

超时等待,让出CPU,不会无期限等待被其他线程唤醒。时间到了可以自动唤醒

TERMINATED(终止)

线程已经终止,可能是正常终止,也可能是异常终止,一般可以终止的操作如下所示:

  1. run()方法执行结束
  2. 线程执行抛出异常终止
  3. 对线程的实例调用stop()方法,现在该方法已经被废弃了。如果我们需要中断run()方法,可以调用interrupt()方法。
Java线程中的阻塞状态(BLOCKED)、无时限等待状态(WAITING)、有时限等待状态(TIMED_WAITING)都是一种状态,即通用线程生命周期中的休眠状态。也就是说,只要Java中的线程处于这三种状态时,那么,这个线程就没有CPU的使用权。

状态的转换

NEW到RUNNABLE状态

调用线程对象的start()方法

RUNNABLE与BLOCKED的状态转换

RUNNABLE转换为BLOCKED只有一种可能:要进入synchronized修饰的方法、代码块,却因为获取不到锁标志,所以变成了阻塞。

RUNNABLE与WAITING状态转换

  1. 获得synchronized隐式锁的线程,调用无参的Object.wait()方法
  2. 调用无参数的Thread.join()方法。例如,在线程A中调用线程B的join()方法,则线程A会等待线程B执行完以后再继续执行。而线程A在等待线程B执行的过程中,其状态会从RUNNABLE转换到WAITING。当线程B执行完毕,线程A的状态则会从WAITING状态转换成RUNNABLE状态。
  3. 调用LockSupport.park()方法,当前线程会阻塞,线程的状态会从RUNNABLE转换成WAITING。调用LockSupport.unpark(Thread thread)可唤醒目标线程,目标线程的状态又会从WAITING状态转换到RUNNABLE

RUNNABLE与TIMED_WAITING状态转换

基本上都是调用带有超时参数的方法,如下所示:

  1. 调用带超时参数的Thread.sleep(long millis)方法;
  2. 获得synchronized隐式锁的线程,调用带超时参数的Object.wait(long timeout)参数;
  3. 调用带超时参数的Thread.join(long millis)方法;
  4. 调用带超时参数的LockSupport.parkNanos(Object blocker, long deadline)方法;
  5. 调用带超时参数的LockSuppor.parkUntil(long deadline)方法

RUNNABLE到TERMINATED状态

  1. run()方法执行结束
  2. 线程执行抛出异常终止
  3. 对线程的实例调用stop()方法,现在该方法已经被废弃了。如果我们需要中断run()方法,可以调用interrupt()方法。

线程常见方法

Object类:wait(), notify(), notifyAll()

Thread类:start(), sleep(), yield(), join()

wait()方法

使用同步对象调用此方法,使当前线程处于等待状态,直到其他线程调用同步对象的notify()方法或 notifyAll() 方法唤醒线程,或者超过设置的超时时间。

方法的两个参数:

  • timeout - 等待时间(以毫秒为单位)
  • nanos - 额外等待时间(以纳秒为单位)

方法有几个注意点:

  1. 超时时间为timeoutnanos之和
  2. timeoutnanos参数都为 0,则不会超时,等同于wait()
  3. 调用方法前必须拥有对象的锁,否则会发生 IllegalMonitorStateException 异常
  4. wait() 方法会释放对象的“锁标志”,失去CPU使用权
  5. 调用wait(),wait(0),wait(0,0)方法后进入WAITIN状态
  6. 有参方法调用后,此线程进入TIMED_WAITING状态

notify()方法

使用同步对象调用此方法,从对象等待池中随机选一个线程移出并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,在当前线程放弃对该对象的锁定之前,被唤醒的线程将无法继续,它们随时准备争夺锁的拥有权。

  1. 调用方法前必须拥有对象的锁,否则会发生 IllegalMonitorStateException 异常
  2. 被唤醒的线程进入BLOCKED 状态,重新竞争锁

notifyAll()方法

使用同步对象调用此方法,唤醒对象等待池中所有的等待线程,让他们加入锁标志等待池中竞争锁。

  1. 调用方法前必须拥有对象的锁,否则会发生 IllegalMonitorStateException 异常
  2. 被唤醒的线程进入BLOCKED 状态,重新竞争锁
wait()notify()notifyAll() 只能在 synchronized 语句中使用,但是如果使用的是 ReenTrantLock 实现同步,该如何达到这三个方法的效果呢?解决方法是使用 ReenTrantLock.newCondition() 获取一个 Condition 类对象,然后 Conditionawait()signal() 以及 signalAll() 分别对应上面的三个方法。

start()方法

启动线程,使用线程的实例调用此方法,JVM会调用此线程的run方法。

  1. 调用方法后进入RUNNABLE状态,失去CPU使用权
  2. 不能多次启动同一线程实例;线程一旦结束,也不能重新启动。两者都会抛出 java.lang.IllegalThreadStateException 异常

sleep(long millis)方法

JDK18 描述如下所示:

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.

使当前执行的线程在指定的毫秒数内休眠(暂时停止执行),这取决于系统计时器和调度程序的精度和准确性。线程不会失去任何监视器的所有权。

  1. 这个方法需要传入参数,表示线程睡眠指定的时间
  2. 调用方法后进入TIMED_WAITING状态,失去CPU使用权
  3. 不会释放“锁标志”,如果有 synchronized 同步块,其他线程仍然不能访问共享数据
  4. 时间到了以后自动唤醒进入RUNNABLE状态

yield()方法

JDK18 描述如下所示:

A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilise a CPU. Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.

It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions. It may also be useful when designing concurrency control constructs such as the ones in the java.util.concurrent.locks package.

此方法是一种尝试改变操作系统的线程调度的方法,调用此方法只是使当前线程重新回到可执行状态,该线程改变状态后可能会被马上执行。

  1. 调用方法后进入操作系统层面的就绪状态
  2. 不会释放“锁标志”
  3. yield() 方法只能使同优先级或者高优先级的线程得到执行机会

join()方法

JDK18 描述如下所示:

Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever.

This implementation uses a loop of this.wait calls conditioned on this.isAlive. As a thread terminates the this.notifyAll method is invoked. It is recommended that applications not use wait, notify, or notifyAll on Thread instances.

A线程中调用B线程的join()方法,则A线程会等待B线程结束以后在继续执行。从源码实现以及jdk文档描述我们可以看出join是基于wait方法实现。

  1. 这个方法可以传入参数,参数为0时相当于无参调用
  2. join()join(0)相等,都是永远等待,调用方法后进入WAITIN状态
  3. 有参方法调用后,此线程进入TIMED_WAITING状态
  4. 会释放“锁标志”
  5. 对已经运行结束的线程调用join()方法会立刻返回

调用方法线程状态变化图

iShot_2022-05-20_00.15.15.png

目录
相关文章
|
10天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
19天前
|
Java API 调度
Java 线程的生命周期
在JDK 1.5之前,线程的生命周期包括五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。JDK 1.5及之后增加了三种阻塞状态,共六种状态:新建、可运行、终止、锁阻塞、计时等待和无限等待。这些状态描述了线程在操作系统和JVM中的不同阶段。
Java 线程的生命周期
|
19天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
7天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
27 9
|
10天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
7天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
10天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
24 3
|
8天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
9天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
21 1
|
10天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。