详细分析 Java 中启动线程的正确和错误方式
启动线程的正确和错误方式
前文回顾
start 方法和 run 方法的比较
代码演示:
/**
 * <p>
 * start() 和 run() 的比较
 * </p>
 *
 * @author 踏雪彡寻梅
 * @version 1.0
 * @date 2020/9/20 - 16:15
 * @since JDK1.8
 */
public class StartAndRunMethod {
    public static void main(String[] args) {
        // run 方法演示
        // 输出: name: main
        // 说明由主线程去执行的, 不符合新建一个线程的本意
        Runnable runnable = () -> {
            System.out.println("name: " + Thread.currentThread().getName());
        };
        runnable.run();
        // start 方法演示
        // 输出: name: Thread-0
        // 说明新建了一个线程, 符合本意
        new Thread(runnable).start();
    }
}
从以上示例可以分析出以下两点:
- 直接使用 - run方法不会启动一个新线程。(错误方式)
- start方法会启动一个新线程。(正确方式)
start 方法分析
start 方法的含义以及注意事项
- start方法可以启动一个新线程。- 线程对象在初始化之后调用了 - start方法之后, 当前线程(通常是主线程)会请求 JVM 虚拟机如果有空闲的话来启动一下这边的这个新线程。
- 也就是说, 启动一个新线程的本质就是请求 JVM 来运行这个线程。 
- 至于这个线程何时能够运行,并不是简单的由我们能够决定的,而是由线程调度器去决定的。 
- 如果它很忙,即使我们运行了 - start方法,也不一定能够立刻的启动线程。
- 所以说 - srtart方法调用之后,并不意味这个方法已经开始运行了。它可能稍后才会运行,也很有可能很长时间都不会运行,比如说遇到了饥饿的情况。
- 这也就印证了有些情况下,线程 1 先掉用了 - start方法,而线程 2 后调用了- start方法,却发现线程 2 先执行线程 1 后执行的情况。
- 总结: 调用 - start方法的顺序并不能决定真正线程执行的顺序。
- 注意事项 - start方法会牵扯到两个线程。
- 第一个就是主线程,因为我们必须要有一个主线程或者是其他的线程(哪怕不是主线程)来执行这个 - start方法,第二个才是新的线程。
- 很多情况下会忽略掉为我们创建线程的这个主线程,不要误以为调用了 - start就已经是子线程去执行了,这个语句其实是主线程或者说是父线程来执行的,被执行之后才去创建新线程。
 
 
- start方法创建新线程的准备工作- 首先,它会让自己处于就绪状态。 - 就绪状态指已经获取到除了 CPU 以外的其他资源, 如已经设置了上下文、栈、线程状态以及 PC(PC 是一个寄存器,PC 指向程序运行的位置) 等。
 
- 做完这些准备工作之后,就万事俱备只欠东风了,东风就是 CPU 资源。 
- 做完准备工作之后,线程才能被 JVM 或操作系统进一步去调度到执行状态等待获取 CPU 资源,然后才会真正地进入到运行状态执行 - run方法中的代码。
 
- 需要注意: 不能重复的执行 start 方法 - 代码示例 - /**
 * <p>
 * 演示不能重复的执行 start 方法(两次及以上), 否则会报错
 * </p>
 *
 * @author 踏雪彡寻梅
 * @version 1.0
 * @date 2020/9/20 - 16:47
 * @since JDK1.8
 */
 public class CantStartTwice {
 public static void main(String[] args) {
 Runnable runnable = () -> {
 System.out.println("name: " + Thread.currentThread().getName());
 };
 Thread thread = new Thread(runnable);
 // 输出: name: Thread-0
 thread.start();
 // 输出: 抛出 java.lang.IllegalThreadStateException
 // 即非法线程状态异常(线程状态不符合规定)
 thread.start();
 }
 }
 
- 报错的原因 - start一旦开始执行,线程状态就从最开始的 New 状态进入到后续的状态,比如说 Runnable,然后一旦线程执行完毕,线程就会变成终止状态,而终止状态永远不可能再返回回去,所以会抛出以上异常,也就是说不能回到初始状态了。这里描述的还不够清晰,让我们来看看源码能了解的更透彻。
 
 
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 方法
        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 */
        }
    }
}
源码中的流程
第一步:
启动新线程时会首先检查线程状态是否为初始状态, 这也是以上抛出异常的原因。即以下代码:
if (threadStatus != 0)
	throw new IllegalThreadStateException();
其中 threadStatus 这个变量的注释如下,也就是说 Java 的线程状态最初始(还没有启动)的时候表示为 0:
/* Java thread status for tools,
 * initialized to indicate thread 'not yet started'
 */
private volatile int threadStatus = 0;
第二步:
将其加入线程组。即以下代码:
group.add(this);
第三步:
最后调用 start0() 这个 native 方法(native 代表它的代码不是由 Java 实现的,而是由 C/C++ 实现的,具体实现可以在 JDK 里面看到,了解即可), 即以下代码:
boolean started = false;
try {
    // 第三步, 调用 start0 方法
    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 */
    }
}
run 方法分析
run 方法源码分析
@Override
public void run() {
    // 传入了 target 对象(即 Runnable 接口的实现), 执行传入的 target 对象的 run 方法
    if (target != null) {
        target.run();
    }
}
对于 run 方法的两种情况
- 第一种: 重写了 - Thread类的- run方法,- Thread的- run方法会失效, 将会执行重写的- run方法。
- 第二种: 传入了 - target对象(即- Runnable接口的实现),执行- Thread的原有- run方法然后接着执行- target对象的- run方法。
- 总结: - run方法就是一个普通的方法, 上文中直接去执行- run方法也就是相当于我们执行自己写的普通方法一样,所以它的执行线程就是我们的主线程。
- 所以要想真正的启动线程,不能直接调用 - run方法,而是要调用- start方法,其中可以间接的调用- run方法。
 
如有写的不足的,请见谅,请大家多多指教。
详细分析 Java 中启动线程的正确和错误方式的更多相关文章
- 详细分析 Java 中实现多线程的方法有几种?(从本质上出发)
		详细分析 Java 中实现多线程的方法有几种?(从本质上出发) 正确的说法(从本质上出发) 实现多线程的官方正确方法: 2 种. Oracle 官网的文档说明 方法小结 方法一: 实现 Runnabl ... 
- java 中创建线程有哪几种方式?
		Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ... 
- 注解式项目开发!详细解析Java中各个注解的作用和使用方式
		@Target 作用: 指明了修饰的这个注解的使用范围, 即被描述的注解可以用在哪里 @Target(ElementType.Type) ElementType取值的类型: TYPE: 类,接口或者枚 ... 
- 【Java中的线程】java.lang.Thread 类分析
		进程和线程 联想一下现实生活中的例子--烧开水,烧开水时是不是不需要在旁边守着,交给热水机完成,烧开水这段时间可以去干一点其他的事情,例如将衣服丢到洗衣机中洗衣服.这样开水烧完,衣服洗的也差不多了.这 ... 
- 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!
		碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ... 
- Java中的线程
		http://hi.baidu.com/ochzqvztdbabcir/item/ab9758f9cfab6a5ac9f337d4 相濡以沫 Java语法总结 - 线程 一 提到线程好像是件很麻烦很复 ... 
- [转]详细介绍java中的数据结构
		详细介绍java中的数据结构 本文介绍的是java中的数据结构,本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类.一起来看本文吧! 也许你已经熟练使用了java.util包里面的各 ... 
- 详细介绍java中的数据结构
		详细介绍java中的数据结构 http://developer.51cto.com/art/201107/273003.htm 本文介绍的是java中的数据结构,本文试图通过简单的描述,向读者阐述各个 ... 
- 转:二十一、详细解析Java中抽象类和接口的区别
		转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ... 
随机推荐
- 【趣味设计模式系列】之【代理模式3--Cglib动态代理源码解析】
			1. 图解 上图主要描述了Cglib动态代理的主要执行过程,下面做详细分析,以下源码使用的Cglib版本为3.2.12. 2. Enhancer源码分析 public Object create() ... 
- Gradle Wrapper
			Gradle Wrapper 当把本地一个项目放入到远程版本库的时候,如果这个项目是以gradle构建的,那么其他人从远程仓库拉取代码之后如果本地没有安装过gradle会无法编译运行,如果对gradl ... 
- IDEA的Debug详解
			01_Debug简介和意义 什么是程序DeBug? Debug,是程序开发人员必会的一项调试程序的技能. 企业中程序开发和程序调试的比例为1:1.5,可以说如果你不会调试程序,你就没有办法从事编程工作 ... 
- 起源seo为何要做seo培训
			http://www.wocaoseo.com/thread-91-1-1.html 焦大,在2010年末左右开始接触seo,2011年3月份正式开始做seo,到如今做seo已经3年了,实话说我没有其 ... 
- 揭秘日活千万腾讯会议全量云原生化上TKE技术实践
			腾讯会议,一款联合国都Pick的线上会议解决方案,提供完美会议品质和灵活协作空间,广泛应用在政府.医疗.教育.企业等各个行业.大家从文章8天扩容100万核,腾讯会议是如何做到的?都知道腾讯会议背后的计 ... 
- [ASP.NET Core开发实战]基础篇02 依赖注入
			ASP.NET Core的底层机制之一是依赖注入(DI)设计模式,因此要好好掌握依赖注入的用法. 什么是依赖注入 我们看一下下面的例子: public class MyDependency { pub ... 
- windows快速安装linux虚拟机
			1. 场景描述 因测试中需要linux集群,目前的服务器不太方便部署,需要本机(windows7)启动多个linux虚拟机,记录下,希望能帮到需要的朋友. 2. 解决方案 2.1 软件准备 (1)使用 ... 
- 【Pytorch-入门】windows下的环境搭建(经验证成功~)
			前言 实验需要,之前使的tensorflow[因为自己手边的服务器都是windows环境TT...],但身边的师兄们用的都是pytorch,自己查了查现在做科研基本上都是用的pytorch,而且现在p ... 
- Unity坑之 加了Rigidbody后主角反而朝天上飞?
			今儿在做项目的时候,给主角加上一个Rigidbody组件,设置如下图: 然后问题来了,我本来是想让主角通过重力控制,掉到地上,但是加上之后,主角反而朝着天上飞! 这TM什么鬼? 经过多番探查,发现是A ... 
- 哈希,hash
			Hash,一般翻译做散列.杂凑,或音译为哈希.----摘自百度百科 先来看个题:给你一坨一些键值集<key,value>,\(key\)的范围是\([1,10^{10}]\),每次询问\( ... 
