为什么有些人说JAVA线程五种状态,有些人说六种?
mhr18 2024-10-02 16:48 26 浏览 0 评论
Java线程是Java并发编程的基础,理解Java线程的生命周期对于编写高效、稳定的并发程序至关重要。本文将从两个角度来介绍Java线程的生命周期,并通过代码示例进行验证。
一、复习:
在Java中,线程的创建主要通过两种方式:继承Thread类或实现Runnable接口、Callnablee接口。以下是一个简单的示例:
1.1 创建线程的方式
1.1.1 创建建方式一:继承Thread类
步骤:
- 创建自定义类继承于Thread类,并重写Thread类的run()方法。该run()方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
- 创建自定义类(Thread子类)的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动该线程。
/**
* Java中创建线程方式一:继承Thread类
*/
public class ThreadTest extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
}
}
打印结果:
0
1
2
3
4
5
6
7
8
9
1.1.2 创建方式二:实现Runnable接口
步骤:
- 创建自定义类实现于Runnable接口,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建自定义类(Runnable实现类)的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动该线程。
/**
* Java中创建线程方式二:实现Runnable接口
*/
public class RunnableTest implements Runnable{
@Override
public void run() {
for (int i = 100; i < 110; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
thread.start();
}
}
打印结果:
100
101
102
103
104
105
106
107
108
109
1.1.3 创建方式三:通过Callable和Future创建线程
Callable和Future出现的背景
一般创建线程时,使用上面两种方式居多。但是这两种方式都有一个缺陷:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
Callable和Future简介
Callable接口可以理解成一段可以调用并返回结果的代码(call方法);
Future接口表示异步任务,是还没有完成的任务给出的未来结果。
所以说Callable用于产生结果,Future用于获取结果。这点可以在源码里面分析得知。
源码分析
先看Runnable源码
Runnable位于java.lang包下,它是一个接口,在它里面声明了一个方法叫做 run():
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
再看Callable源码
Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
Future源码
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。为什么这么说呢?看了它的源码就知道了。
Future类位于java.util.concurrent包下,它也是一个接口
package java.util.concurrent;
public interface Future<V> {
/**
* 取消任务
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* 任务是否被取消成功
*/
boolean isCancelled();
/**
* 任务是否已经完成
*/
boolean isDone();
/**
* 获取执行结果
*/
V get() throws InterruptedException, ExecutionException;
/**
* 获取执行结果,支持超时
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
所以说Future一共给我们提供了三种功能:
- 能够取消任务。
- 判断任务是否完成。
- 能够获取任务执行结果。
但是因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
FutureTask实现于RunnableFuture接口,这个接口的定义如下:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看到这个接口实现了Runnable和Future接口,接口中的具体实现由FutureTask来实现。这个类的两个构造方法如下 :
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
如上提供了两个构造函数,一个以Callable为参数,另外一个以Runnable为参数。这些类之间的关联允许你基于FutureTask的Runnable特性(因为它实现了Runnable接口),把任务写成Callable,然后封装进一个由执行者调度并在必要时可以取消的FutureTask。
FutureTask可以由执行者调度,它对外提供的方法基本上就是Future和Runnable接口的组合:get()、cancel、isDone()、isCancelled()和run(),而run()方法通常都是由执行者调用,我们基本上不需要直接调用它。
通过Callable和Future创建一个线程
步骤:
- 创建自定义类实现于Callable接口,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
- 创建自定义类(Callable实现类)的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
示例:
/**
* Java中创建线程方式三:Callable和FutureTask结合使用
*/
public class CallableTest implements Callable{
@Override
public Object call() throws Exception {
int i = 1000;
for ( ; i < 1010; i++) {
System.out.println(i);
}
return 1111;
}
public static void main(String[] args) {
CallableTest callableTest = new CallableTest();
FutureTask<Integer> futureTask = new FutureTask<Integer>(callableTest);
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println("Result:"+futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
打印结果
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
Result:1111
1.2 面试题:Runable接口和Calla
返回值:
- Runnable接口的run()方法没有返回值,它表示一个没有返回结果的任务。
- Callable接口的call()方法有返回值,可以返回计算结果。
异常处理:
- Runnable接口的run()方法不能抛出受检查异常,只能通过捕获异常并在方法内部处理。
- Callable接口的call()方法可以抛出受检查异常,调用者需要捕获并处理异常。
使用方式
- Runnable接口通常用于执行没有返回结果的任务,可以通过Thread类的构造函数来创建线程并传递一个Runnable对象。
- Callable接口通常用于执行有返回结果的任务,需要配合ExecutorService接口或Future接口来提交和执行任务。
返回结果获取
- Runnable接口没有提供直接获取任务执行结果的方法。
- Callable接口的call()方法返回一个Future对象,通过该对象可以获取任务的执行结果。ble接口的区别
二、线程池的生命周期
Java线程的状态可以被划分为五种或六种,这主要取决于你从哪个角度来看。在操作系统的传统线程模型中,线程通常被分为五种状态。
2.1 从JVM源代码看线程周期:
- 初始 (NEW) :新创建了一个线程对象,但还没有调用start ()方法3。
- 运行 (RUNNABLE) :Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
- 阻塞 (BLOCKED) :表示线程阻塞于锁3。
- 等待 (WAITING) :进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待 (TIMED_WAITING) :该状态不同于WAITING,它可以在指定的时间后自行返回
- 终止 (TERMINATED) :表示该线程已经执行完毕
2.2 从操作系统的层面来看:
- 新建状态 (New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread ()2。
- 就绪状态 (Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start ()方法,从而来启动该线程。例如,thread.start ()。处于就绪状态的线程,随时可能被CPU调度执行2。
- 运行状态 (Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态2。
- 阻塞状态 (Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:等待阻塞、同步阻塞和其他阻塞2。
- 死亡状态 (Dead): 线程执行完了或者因异常退出了run ()方法,该线程结束生命周期2。
总结:
操作系统层面的五种线程状态和JVM的六种线程状态是两个不同层次的概念,它们之间并不是一一对应的关系。
JVM并不关心操作系统线程的实际状态,从JVM看来,等待CPU使用权(操作系统状态为可运行态)与等待I/O(操作系统处于等待状态)没有区别,都是在等待某种资源,所以都归入RUNNABLE状态。因此,操作系统层面的线程状态并不直接影响JVM的线程状态。
这两者的主要区别在于它们关注的焦点不同:操作系统更关注线程对CPU和I/O资源的使用,而JVM更关注线程在Java程序中的行为。
在「JDK1.2之后」,Java线程模型已经确定了基于操作系统原生线程模型实现。因此,目前或者今后的JDK版本中,操作系统支持怎么样的线程模型,在很大程度上决定了Java虚拟机的线程如何映射,这一点在不同的平台上没有办法达成一致,虚拟机规范中也未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对于Java程序来说,这些差异是透明的。
对应Oracle Sun JDK或者说Oracle Sun JVM而言,它的Windows版本和Linux版本都是使用「一对一的线程模型」实现的。
一对一的线程模型也就是一条Java线程就映射到一条轻量级进程(「Light Weight Process」)中,而一条轻量级线程又映射到一条内核线程(「Kernel-Level Thread」)。我们平时所说的线程,往往就是指轻量级进程(或者通俗来说我们平时新建的java.lang.Thread就是轻量级进程实例的一个"句柄",因为一个java.lang.Thread实例会对应JVM里面的一个JavaThread实例,而JVM里面的JavaThread就应该理解为轻量级进程)。推算这个线程映射关系,可以知道,我们在应用程序中创建或者操作的java.lang.Thread实例最终会映射到系统的内核线程,如果我们恶意或者实验性无限创建java.lang.Thread实例,最终会影响系统的正常运行甚至导致系统崩溃(可以在Windows开发环境中做实验,确保内存足够的情况下使用死循环创建和运行java.lang.Thread实例)。
相关推荐
- Java面试宝典之问答系列(java面试回答)
-
以下内容,由兆隆IT云学院就业部根据多年成功就业服务经验提供:1.写出从数据库表Custom中查询No、Name、Num1、Num2并将Name以姓名显示、计算出的和以总和显示的SQL。SELECT...
- ADG (Active Data Guard) 数据容灾架构下,如何配置 Druid 连接池?
-
如上图的数据容灾架构下,上层应用如果使用Druid连接池,应该如何配置,才能在数据库集群节点切换甚至主备数据中心站点切换的情况下,上层应用不需要变动(无需修改配置也无需重启);即数据库节点宕机/...
- SpringBoot多数据源dynamic-datasource快速入门
-
一、简介dynamic-datasourc是一个基于SpringBoot的快速集成多数据源的启动器,其主要特性如下:支持数据源分组,适用于多种场景纯粹多库读写分离一主多从混合模式。支持...
- SpringBoot项目快速开发框架JeecgBoot——项目简介及系统架构!
-
项目简介及系统架构JeecgBoot是一款基于SpringBoot的开发平台,它采用前后端分离架构,集成的框架有SpringBoot2.x、SpringCloud、AntDesignof...
- 常见文件系统格式有哪些(文件系统类型有哪几种)
-
PART.01常见文件系统格式有哪些常见的文件系统格式有很多,通常根据使用场景(Windows、Linux、macOS、移动设备、U盘、硬盘等)有所不同。以下是一些主流和常见的文件系统格式及其特点:一...
- Oracle MySQL Operator部署集群(oracle mysql group by)
-
以下是使用OracleMySQLOperator部署MySQL集群的完整流程及关键注意事项:一、部署前准备安装MySQLOperator通过Helm安装Operator到Ku...
- LibreOffice加入"转向Linux"运动
-
LibreOffice项目正准备削减部分Windows支持,并鼓励用户切换到Linux系统。自Oracle放弃OpenOffice后,支持和指导LibreOffice开发的文档基金会对未来有着明确的观...
- Oracle Linux 10发布:UEK 8.1、后量子加密、增强开发工具等
-
IT之家6月28日消息,科技媒体linuxiac昨日(6月27日)发布博文,报道称OracleLinux10正式发布,完全二进制兼容(binarycompatibility...
- 【mykit-data】 数据库同步工具(数据库同步工具 开源)
-
项目介绍支持插件化、可视化的数据异构中间件,支持的数据异构方式如下MySQL<——>MySQL(增量、全量)MySQL<——>Oracle(增量、全量)Oracle...
- oracle关于xml的解析(oracle读取xml节点的属性值)
-
有时需要在存储过程中处理xml,oracle提供了相应的函数来进行处理,xmltype以及相关的函数。废话少说,上代码:selectxmltype(SIConfirmOutput).extract...
- 如何利用DBSync实现数据库同步(通过dblink同步数据库)
-
DBSync是一款通用型的数据库同步软件,能侦测数据表之间的差异,能实时同步差异数据,从而使双方始终保持一致。支持各种数据库,支持异构同步、增量同步,且提供永久免费版。本文介绍其功能特点及大致用法,供...
- MYSQL存储引擎InnoDB(八十):InnoDB静态数据加密
-
InnoDB支持独立表空间、通用表空间、mysql系统表空间、重做日志和撤消日志的静态数据加密。从MySQL8.0.16开始,还支持为模式和通用表空间设置加密默认值,这允许DBA控制在这些模...
- JDK高版本特性总结与ZGC实践(jdk高版本兼容低版本吗)
-
美团信息安全技术团队核心服务升级JDK17后,性能与稳定性大幅提升,机器成本降低了10%。高版本JDK与ZGC技术令人惊艳,且JavaAISDK最低支持JDK17。本文总结了JDK17的主要...
- 4 种 MySQL 同步 ES 方案,yyds!(两个mysql数据库自动同步的方法)
-
本文会先讲述数据同步的4种方案,并给出常用数据迁移工具,干货满满!不BB,上文章目录:1.前言在实际项目开发中,我们经常将MySQL作为业务数据库,ES作为查询数据库,用来实现读写分离,...
- 计算机Java培训课程包含哪些内容?其实就这六大块
-
不知不觉秋天已至,如果你还处于就业迷茫期,不如来学习Java。对于非科班小白来说,Java培训会更适合你。提前了解下计算机Java培训课程内容,会有助于你后续学习。下面,我就从六个部分为大家详细介绍...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Java面试宝典之问答系列(java面试回答)
- ADG (Active Data Guard) 数据容灾架构下,如何配置 Druid 连接池?
- SpringBoot多数据源dynamic-datasource快速入门
- SpringBoot项目快速开发框架JeecgBoot——项目简介及系统架构!
- 常见文件系统格式有哪些(文件系统类型有哪几种)
- Oracle MySQL Operator部署集群(oracle mysql group by)
- LibreOffice加入"转向Linux"运动
- Oracle Linux 10发布:UEK 8.1、后量子加密、增强开发工具等
- 【mykit-data】 数据库同步工具(数据库同步工具 开源)
- oracle关于xml的解析(oracle读取xml节点的属性值)
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle 空为0 (51)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)