为什么我们调用 start()方法时会执行 run()方法 ,为什么我们不能 直接调用 run()方法?

1. 引言

多线程编程是现代计算机编程中非常重要的一部分,尤其是在 Java 语言中,线程的创建和管理是实现并发的重要手段。在 Java 中,我们通常通过调用 start() 方法来启动一个线程,这会间接地执行 run() 方法。那么,为什么不能直接调用 run() 方法,而必须通过 start() 方法呢?本文将通过对 start()run() 的详细解析,深入探讨这个问题。

2. 线程与并发的基本概念

线程是程序执行的最小单元,多线程指的是一个程序有多个执行流同时进行。这些执行流(线程)可以在多核处理器上并行运行,或者在单核处理器上通过时间片轮转实现并发执行。Java 中的线程类 Thread 和接口 Runnable 是实现多线程编程的主要方式。

并发与并行

并发(Concurrency)和并行(Parallelism)是多线程中两个重要的概念。并发意味着多个任务交替进行,而并行则意味着多个任务同时进行。Java 通过线程机制提供了这两种能力的支持。

3. 什么是 start() 方法

start() 是 Java Thread 类中的一个方法,它用于启动一个新线程。当调用 start() 方法时,Java 虚拟机(JVM)会创建一个新的执行路径,并调用线程的 run() 方法。

具体来说,start() 方法:

  • 分配系统资源(如内存、CPU 线程)以启动新线程。
  • 将当前线程添加到线程调度器中,由调度器决定执行时间。
  • 一旦 start() 被调用,新线程处于就绪状态,准备开始执行 run() 方法中的代码。
Thread thread = new Thread(() -> {System.out.println("Running in a new thread");
});
thread.start();  // 这里调用了 start(),而不是直接调用 run()

4. 什么是 run() 方法

run() 方法是 Java 中 Thread 类或 Runnable 接口中的一个方法,它包含了线程执行的逻辑代码。当你创建一个线程并调用 start() 方法时,run() 方法会被执行。

如果直接调用 run() 方法,比如 thread.run(),那么该方法不会在新的线程中运行,而是会在调用它的线程中执行。

public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Running in run()");}
}public class Main {public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.run();  // 这只是在主线程中调用了 run(),而不是在新的线程中运行}
}

在上面的例子中,thread.run() 只是在主线程中运行 run() 方法,相当于普通方法调用,没有启动新线程。

5. start()run() 的区别

调用 start() 方法

  • 调用 start() 方法会启动一个新线程,这意味着会分配一个新的执行路径,并在该路径中调用 run() 方法。
  • 调用 start() 后,线程处于就绪状态,等待 JVM 调度器安排 CPU 时间片来执行线程逻辑。
  • 每个线程都独立运行,和其他线程并行或并发。

调用 run() 方法

  • 调用 run() 方法不会启动新线程,而只是一个普通方法调用。
  • run() 方法中的代码在当前线程中执行,没有并发优势。
  • 调用 run() 不能充分利用多核 CPU 的优势,也不会将线程加入调度队列。

6. 为什么不能直接调用 run() 方法

  • 线程特性丧失:调用 run() 方法时,线程特性丧失。它只是一个简单的方法调用,不涉及创建新线程。
  • 没有并行性:直接调用 run(),会使得代码在主线程中顺序执行,而非并发执行,从而无法利用多线程带来的并行性优势。
  • 不能获得独立的执行环境start() 方法会为每个线程分配一个独立的执行环境,确保线程互相独立。而直接调用 run() 会导致所有代码都在同一个线程中执行,无法隔离任务。

7. Java 中 ThreadRunnable 接口的设计思路

在 Java 中,Thread 类和 Runnable 接口是实现多线程的基础。Java 推荐实现 Runnable 接口而不是继承 Thread,以便更好地实现代码的复用性和解耦。Runnable 接口的实现类可以通过传递给 Thread 对象来启动新线程:

Runnable runnableTask = () -> {System.out.println("Task running");
};
Thread thread = new Thread(runnableTask);
thread.start();

8. 线程生命周期与状态

Java 中,线程的生命周期包括以下几种状态:新建(NEW)、就绪(RUNNABLE)、运行(RUNNING)、阻塞(BLOCKED)、等待(WAITING)以及终止(TERMINATED)。调用 start() 方法会使线程进入就绪状态,等待调度器的分配,而直接调用 run() 并不会引发这些状态的变化。

9. 多线程的常见问题及解决方案

  • 线程安全问题:多个线程访问共享资源时可能会发生冲突,例如多个线程同时修改变量。
  • 死锁:两个或多个线程相互等待对方释放锁,导致无限期阻塞。
  • 上下文切换:频繁的上下文切换会导致性能问题。

可以通过 同步代码块重入锁(ReentrantLock)原子类(Atomic Classes) 来解决这些问题。

10. 使用场景与最佳实践

  • 使用 start() 启动新线程:当任务需要并行执行时,应使用 start() 方法启动新线程,以充分利用 CPU 资源。
  • 避免直接调用 run()run() 方法只能在测试或调试时使用,不适合用于生产中的多线程执行。
  • 线程池的使用:使用 ExecutorService 管理线程,减少系统创建和销毁线程的开销。
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {System.out.println("Task is running in a thread pool");
});
executor.shutdown();

11. 示例代码与实战

以下是一个完整的示例,演示如何正确地启动线程与错误地直接调用 run() 方法的区别:

public class ThreadExample {public static void main(String[] args) {Thread thread1 = new Thread(() -> System.out.println("Thread1 running with start()"));thread1.start();  // 通过 start() 启动新线程Thread thread2 = new Thread(() -> System.out.println("Thread2 running with run()"));thread2.run();  // 直接调用 run(),在主线程中运行}
}

输出分析

  • thread1.start() 会在一个新线程中打印 “Thread1 running with start()”
  • thread2.run() 会在主线程中打印 “Thread2 running with run()”

12. 总结

Java 中调用 start() 方法用于启动新线程,而直接调用 run() 方法只是在当前线程中执行普通方法。通过 start() 方法,JVM 会分配新线程来执行任务,实现并行和提高系统性能。而 run() 方法仅在当前线程中调用,失去了线程独立的特性。了解 start()run() 之间的区别,是掌握 Java 多线程编程的关键。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/464378.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RV1126-SDK学习之OSD实现原理

RV1126-SDK学习之OSD实现原理 前言 本文简单记录一下我在学习RV1126的SDK当中OSD绘制的原理的过程。 在学习OSD的过程当中,可能需要补充的基础知识: OSD是什么? BMP图像文件格式大致组成? 图像调色(Palette&#…

Vehicle OS软件平台解决方案

在智能汽车快速迭代的趋势之下,广义操作系统Vehicle OS应运而生,针对应用软件开发周期缩短和底层硬件迭代速度加快的背景,Vehicle OS将应用软件开发和底层硬件迭代解耦。它降低了迭代工作量并节约成本,用标准化的接口来助力软件定…

Chromium Mojo(IPC)进程通信演示 c++(1)

网上搜索关于mojo教程 多数都是理论 加上翻译谷歌mojo文档的,但是如何自定义两个进程使用mojo通信呢?看下面的完整例子介绍:(本人也是参考谷歌代码例子改编而成) 本文演示了client.exe和service.exe 通过mojo::Incomin…

sparkSQL面试题

一、查询所有数学课程成绩大于语文课程成绩的学生学号 数据 1,yuwen,43 1,shuxue,55 2,yuwen,77 2,shuxue,88 3,yuwen,98 3,shuxue,65 3,yingyu,88 基本步骤: 进行行转列比较语文与数学的成绩 SQL代码: with t1 as(SELECT id,sum(if(name yuwen,sc…

算法|牛客网华为机试21-30C++

牛客网华为机试 上篇:算法|牛客网华为机试10-20C 文章目录 HJ21 简单密码HJ22 汽水瓶HJ23 删除字符串中出现次数最少的字符HJ24 合唱队HJ25 数据分类处理HJ26 字符串排序HJ27 查找兄弟单词HJ28 素数伴侣HJ29 字符串加解密HJ30 字符串合并处理 HJ21 简单密码 题目描…

浅谈QT中Tab键的切换逻辑

浅谈QT中Tab键的切换逻辑 无意中发现在输入界面中按下Tab键时,没有按照预想的顺序切换焦点事件,如下图所示 这个现象还是很有趣,仔细观察了下,默认的切换顺序是按照控件拖入顺序,那么知道了这个问题想要解决起来就很简…

科研绘图系列:R语言组合连线图和箱线图(linechart+boxplot)

文章目录 介绍加载R包数据数据预处理画图1画图2系统信息介绍 连线图(Line Chart)是一种常用的数据可视化图表,它通过将一系列数据点用直线段连接起来来展示数据随时间或有序类别变化的趋势。以下是连线图可以表示的一些内容: 时间序列数据:展示数据随时间变化的趋势,例如…

PKG_CHECK_MODULES(FUSE,fuse)

运行 ./configure 命令报错如下: ./configure: line 13934: syntax error near unexpected token FUSE,fuse ./configure: line 13934: PKG_CHECK_MODULES(FUSE,fuse)解决方案: 命令窗口运行如下命令,安装 pkg-config: sudo …

react18中redux-promise搭配redux-thunk完美简化异步数据操作

用过redux-thunk的应该知道,操作相对繁琐一点,dispatch本只可以出发plain object。redux-thunk让dispatch可以返回一个函数。而redux-promise在此基础上大大简化了操作。 实现效果 关键逻辑代码 store/index.js import { createStore, applyMiddlewar…

Lucene分析器的详细使用(5)

文章目录 第5章 分析器5.1 分析器的组成5.1.1 字符过滤器1)HTMLStripCharFilter2)PatternReplaceCharFilter3)MappingCharFilter4)Luke使用字符过滤器 5.1.2 分词器1)StandardTokenzier2)keywordTokenizer3…

selinux和防火墙

SElinux 1、selinux代表的什么? SELinux是Security-Enhanced Linux的缩写,意思是安全强化的linux。 SELinux 主要由美国国家安全局(NSA)开发,当初开发的目的是为了避免资源的误用。 SELinux是对程序、文件等权限设置依…

CentOS 7 安装 ntp,自动校准系统时间

1、安装 ntp yum install ntp 安装好后,ntp 会自动注册成为服务,服务名称为 ntpd 2、查看当前 ntpd 服务的状态 systemctl status ntpd 3、启动 ntpd 服务、查看 ntpd 服务的状态 systemctl start ntpdsystemctl status ntpd 4、设置 ntpd 服务开机启…

Oracle OCP认证考试考点详解082系列11

题记: 本系列主要讲解Oracle OCP认证考试考点(题目),适用于19C/21C,跟着学OCP考试必过。 51. 第51题: 题目 51.View the Exhibit and examine the description of the tables You execute this SQL statement Whi…

C#属性 Property

属性Property不是变量。 它们是由名为访问器方法来实现的一种方法。 实例属性表示的是实例的某个数据,通过这个数据反映实例当前的状态 静态属性表示的是类型的某个数据,通过这个数据反映类型当前的状态 意义: 防止恶意赋值(通过属性间接访问…

Spring框架的事务管理

目录 一、spring框架事务管理相关的类 1.PlatformTransactionManager接口 2.TransactionDefinition接口 二、spring框架声明式事务管理 1.配置文件的方式 (1)配置文件 (2)业务层 (3)持久层 &#…

angular实现list列表和翻页效果

说明:angular实现list列表和翻页效果 上一页 当前页面 下一页 效果图: step1: E:\projectgood\ajnine\untitled4\src\app\car\car.component.css .example-form-fields {display: flex;align-items: flex-start; }mat-list-item{background: antiquew…

PHP常量

PHP 中的常量是指一旦定义后将不能被改变的标识符。 常量可以用const和define()来定义。 PHP常量的特性 不变性: 常量一旦定义,其值不能改变。全局作用域: 常量在定义后,可以在整个脚本的任何地方使用,无需使用 glo…

服务器作业(2)

架设一台NFS服务器,并按照以下要求配置 关闭防火墙 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 配置文件设置: [rootlocalhost ~]# vim /etc/exports 1、开放/nfs/shared目录,供所有用户查询资料 共享…

云轴科技ZStack在CID大会上分享VF网卡热迁移技术

近日,2024中国云计算基础架构开发者大会(以下简称CID大会)在北京举行。此次大会集中展示了云计算基础架构技术领域最前沿的科创成果,汇聚众多的技术专家和行业先锋,共同探讨云计算基础设施的最新发展和未来趋势。云轴科…

【Linux】命令行参数 | 环境变量

🪐🪐🪐欢迎来到程序员餐厅💫💫💫 主厨:邪王真眼 主厨的主页:Chef‘s blog 所属专栏:青果大战linux 总有光环在陨落,总有新星在闪烁 前几天在搞硬件&…