掌握Java线程状态:从NEW到TERMINATED

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 本文探讨了操作系统与Java中线程的状态及其转换。操作系统层面,线程状态包括初始、就绪、运行、阻塞和终止。Java线程状态则细分为NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,并详细介绍了各状态的特性和转换条件。此外,还列举了Java中常用的线程方法,如`wait()`、`notify()`、`start()`和`join()`等,帮助理解线程控制机制。

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

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

操作系统的线程状态

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

初始状态(创建)

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

就绪状态

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

运行状态

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

阻塞状态

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

终止状态

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

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()方法会立刻返回


转载来源:https://juejin.cn/post/7099483629847969806

相关文章
|
3月前
|
Java
【Java基础面试三十二】、new String(“abc“) 是去了哪里,仅仅是在堆里面吗?
这篇文章解释了Java中使用`new String("abc")`时,JVM会将字符串直接量"abc"存入常量池,并在堆内存中创建一个新的String对象,该对象会指向常量池中的字符串直接量。
|
6月前
|
存储 监控 Java
Java输入输出:什么是NIO(New I/O)?
Java输入输出:什么是NIO(New I/O)?
61 1
|
6月前
|
Java
关于java获取时间 new Date(),显示“上午、下午”
关于java获取时间 new Date(),显示“上午、下午”
97 0
|
6月前
|
存储 监控 Java
深入探索Java语言的NIO(New I/O)技术
深入探索Java语言的NIO(New I/O)技术
|
3月前
|
Java
【Java基础面试二十八】、使用字符串时,new和““推荐使用哪种方式?
这篇文章讨论了在Java中使用字符串时,推荐使用双引号`""`直接量方式而不是使用`new`操作符,因为`new`会在常量池之外额外创建一个对象,导致更多的内存占用。
|
4月前
|
Java
Java之file,创建文件,File f1 = new File(“E:\\itcast\\java.txt“),先f1定路径,在f1.createNewFile()就能够创建文件,mkdir目录
Java之file,创建文件,File f1 = new File(“E:\\itcast\\java.txt“),先f1定路径,在f1.createNewFile()就能够创建文件,mkdir目录
|
6月前
|
存储 监控 Java
Java输入输出:什么是NIO(New I/O)?
Java NIO是一种高效I/O库,特征包括非阻塞性操作、通道(如文件、网络连接)、缓冲区和选择器。选择器监控通道状态变化,通知应用程序数据可读写,避免轮询,提升性能。示例代码展示了一个使用NIO的服务器,监听连接、读取数据并处理客户端通信。
52 1
|
6月前
|
Java Linux iOS开发
8 种 Java- 内存溢出之五 -Unable to create new native thread
8 种 Java- 内存溢出之五 -Unable to create new native thread
|
11天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
1天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
下一篇
无影云桌面