Java 多线程启动为什么调用 start() 方法而不是 run() 方法?
Java 多线程启动为什么调用 start() 方法而不是 run() 方法?
多线程在工作中多多少少会用到,我们知道启动多线程调用的是 start() 方法,而不是 run() 方法,你知道原因吗?
在探讨这个问题之前,我们先来了解一些多线程的基础知识~
线程的状态
Java 中,定义了 6 种线程状态,在 Thread 类可以找到:
// 为了节约空间,我删除了注释
public enum State {
NEW,//初始状态 RUNNABLE,//运行状态 BLOCKED,// 阻塞状态 WAITING,//等待状态 TIMED_WAITING,//超时等待状态 TERMINATED;//终止状态
}
这 6 种状态之间的关联,可以看下面这张图:
这张图描述的还是非常详细的,结合这张图,来说说这几种状态分别代表着什么意思:
1、NEW 表示线程创建成功,但没有运行,在 new Thread 之后,没有 start 之前,线程都处于 NEW 状态;
2、RUNNABLE 表示线程正在运行中,当我们运行 strat 方法,子线程被创建成功之后,子线程的状态变成 RUNNABLE;
3、TERMINATED 表示线程已经运行结束,子线程运行完成、被打断、被中止,状态都会从 RUNNABLE 变成 TERMINATED;
4、BLOCKED 表示线程被阻塞,如果线程正好在等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 变成 BLOCKED;
5、 WAITING 和 TIMED_WAITING 都表示等待,现在在遇到 Object#wait、Thread#join、
LockSupport#park 这些方法时,线程就会等待另一个线程执行完特定的动作之后,才能结
束等待,只不过 TIMED_WAITING 是带有等待时间的;
优先级
优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行。
在 Java 源码中,优先级从低到高分别是 1 到 10,线程默认 new 出来的优先级都是 5,源码如下:
/**
- The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
- The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
- The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
线程得创建方式
我们创建多线程有两种方式,一种是继承 Thread 类,另一种是实现 Runnable 接口。两种方式的使用,如下所示:
1、继承 Thread,成为 Thread 的子类
public class MyThread extends Thread{
@Override public void run() { System.out.println("我是通过继承 Thread 类实现的~"); } public static void main(String[] args) { MyThread thread = new MyThread(); // 启动线程 thread.start(); }
}
2、实现 Runnable 接口
public class MyThread1 {
public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("我是通过 runnable 方式实现的~"); } }); // 启动线程 thread.start(); }
}
不管使用哪一种方式,启动线程都是thread.start()方法,如果你做过实验的话,你会发现 thread.run()也可以执行,为什么就一定需要调用thread.start()方法呢?
先说说结论:首先通过对象.run()方法可以执行方法,但是不是使用的多线程的方式,就是一个普通的方法,要想实现多线程的方式,一定需要通过对象.start()方法。
想要弄明白一个问题,最好的办法就是从源码入手,我们也从这两个方法的源码开始,先来看看 start 方法的源码:
public synchronized void start() {
/** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ // 没有初始化,抛出异常 if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); // 是否启动的标识符 boolean started = false; try { // start0() 是启动多线程的关键 // 这里会创建一个新的线程,是一个 native 方法 // 执行完成之后,新的线程已经在运行了 start0(); // 主线程执行 started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } }
}
start 方法的源码也没几行代码,注释也比较详细,最主要的是 start0() 方法,这个后面在解释。再来看看 run() 方法的源码:
@Override public void run() { // 简单的运行,不会新起线程,target 是 Runnable if (target != null) { target.run(); } }
run() 方法的源码就比较简单的,就是一个普通方法的调用,这也印证了我们上面的结论。
接下来我们就来说一说这个 start0() 这个方法,这个是真正实现多线程的关键,start0() 代码如下:
private native void start0();
start0 被标记成 native ,也就是本地方法,并不需要我们去实现或者了解,为什么 start0() 会标记成 native ?
这个要从 Java 跨平台说起,看下面这张图:
start() 方法调用 start0() 方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于 CPU ,由 CPU 统一调度。
我们又知道 Java 是跨平台的,可以在不同系统上运行,每个系统的 CPU 调度算法不一样,所以就需要做不同的处理,这件事情就只能交给 JVM 来实现了,start0() 方法自然就表标记成了 native。
最后,总结一下,Java 中实现真正的多线程是 start 中的 start0() 方法,run() 方法只是一个普通的方法。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
mica 2.0.1 发布新增最好用的 ip2region boot stater
mica(云母) mica 由如梦技术内部的 lutool(撸秃)演变而来。lutool 诞生于 2017 年,受 jhipster 启发逐步形成一个微服务的核心集。 因 lutool 名称与功能不太符合,故在2019年开源时将其改名为 mica(云母),寓意为云服务的基石。 mica 2.x核心依赖 mica 基于 java 8,没有历史包袱,支持传统 Servlet 和 Reactive(webflux)。采用 mica-auto 自动生成 spring.factories 和 spring-devtools.properties 配置,仅依赖 Spring boot、Spring cloud 全家桶,无第三方依赖。市面上鲜有的微服务核心组件。 依赖 版本 Spring Boot 2.2.x mica 2.0.1 更新说明 :sparkles: 开源 mica-ip2region 组件。 :zap: mica-core 优化 R.isNotSuccess 改为 isFail。 :bug: mica-redis jackson class info。 :bug: mica-redis...
- 下一篇
C# 数据操作系列 - 3. ADO.NET 离线查询
C# 数据操作系列 - 3. ADO.NET 离线查询 前言在上一篇中,我故意留下了查询的示范没讲。虽然说可以通过以下代码获取一个DataReader: IDataReader reader = command.ExecuteReader();然后通过reader一行一行的读取数据,但是我并不推荐这样使用。 在查询这一高频需求上,C#为之做了很多工作,提供了更多的选择。这里介绍一个查询的另一套写法。 离线查询C#在查询上提供了另一种机制,可以一次性从数据库把结果读取到网络缓存区中,直到使用的时候才加载到程序中。 在离线查询里最关键的三个接口或类: IDataAdapter 一种适配器,用来获取数据并填充或更新DataSetDataSet 表示数据在内存中的缓存DataTable 表示内存中一个数据表IDataAdapter用来提供数据,DataSet表示adapter读取的结果集,其中有一个DataTable集合表示执行的SQL查询结果。至于为什么是集合,是因为IDataAdapter允许运行多条查询语句。 好,让我们粗略浏览一下这个三个关键点的属性和方法: IDataAdapter:...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7安装Docker,走上虚拟化容器引擎之路