启动线程的正确和错误方式

前文回顾

  1. 详细分析 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 方法,Threadrun 方法会失效, 将会执行重写的 run 方法。

  • 第二种: 传入了 target 对象(即 Runnable 接口的实现),执行 Thread 的原有 run 方法然后接着执行 target 对象的 run 方法。

  • 总结:

    • run 方法就是一个普通的方法, 上文中直接去执行 run 方法也就是相当于我们执行自己写的普通方法一样,所以它的执行线程就是我们的主线程。

    • 所以要想真正的启动线程,不能直接调用 run 方法,而是要调用 start 方法,其中可以间接的调用 run 方法。


如有写的不足的,请见谅,请大家多多指教。

详细分析 Java 中启动线程的正确和错误方式的更多相关文章

  1. 详细分析 Java 中实现多线程的方法有几种?(从本质上出发)

    详细分析 Java 中实现多线程的方法有几种?(从本质上出发) 正确的说法(从本质上出发) 实现多线程的官方正确方法: 2 种. Oracle 官网的文档说明 方法小结 方法一: 实现 Runnabl ...

  2. java 中创建线程有哪几种方式?

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...

  3. 注解式项目开发!详细解析Java中各个注解的作用和使用方式

    @Target 作用: 指明了修饰的这个注解的使用范围, 即被描述的注解可以用在哪里 @Target(ElementType.Type) ElementType取值的类型: TYPE: 类,接口或者枚 ...

  4. 【Java中的线程】java.lang.Thread 类分析

    进程和线程 联想一下现实生活中的例子--烧开水,烧开水时是不是不需要在旁边守着,交给热水机完成,烧开水这段时间可以去干一点其他的事情,例如将衣服丢到洗衣机中洗衣服.这样开水烧完,衣服洗的也差不多了.这 ...

  5. 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!

    碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...

  6. Java中的线程

    http://hi.baidu.com/ochzqvztdbabcir/item/ab9758f9cfab6a5ac9f337d4 相濡以沫 Java语法总结 - 线程 一 提到线程好像是件很麻烦很复 ...

  7. [转]详细介绍java中的数据结构

    详细介绍java中的数据结构 本文介绍的是java中的数据结构,本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类.一起来看本文吧! 也许你已经熟练使用了java.util包里面的各 ...

  8. 详细介绍java中的数据结构

    详细介绍java中的数据结构 http://developer.51cto.com/art/201107/273003.htm 本文介绍的是java中的数据结构,本文试图通过简单的描述,向读者阐述各个 ...

  9. 转:二十一、详细解析Java中抽象类和接口的区别

    转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...

随机推荐

  1. 黑马新版PYTHON教学课程(全)资料加视频完整版百度网盘资料

    黑马新版PYTHON教学课程(全)资料加视频完整版 无加密,适合0基础人群.基础班+就业班.不用解压在线看 百度网盘地址一 淘宝店地址二

  2. 微信小程序——导航栏组件

    组件内属性详解   属性 类型 默认值 必填 说明 nav-postion String relative 否 导航栏(包含导航栏以及状态栏)的position,可取值relative.fixed.a ...

  3. spring如何创建RESTful Service

    REST REST,是指REpresentational State Transfer,有个精辟的解释什么是RESTful, 看url就知道要什么 看method就知道干什么 看status code ...

  4. ZooKeeper的十二连问,你顶得了嘛?

    前言 一线大厂ZooKeeper的十二连问,你顶得了嘛? 本文已经收录到github ❝ https://github.com/whx123/JavaHome ❞ 1. 面试官:工作中使用过Zooke ...

  5. 利用阿里云服务器免费体验word press博客、个人网站

    本文首发于我的个人博客:https://chens.life/create-wordpress-blog.html 前言 目前市面上有许许多多的虚拟云服务器ECS,例如阿里云.华为云.又拍云等等,他们 ...

  6. 01 fs模块

    1 fs.readFile 异步执行函数 /** fs 读取文件相对路径是相对终端命令行所在的路径 process.cwd()返回终端命令行的绝对路径 * */ fs = require('fs') ...

  7. 使用kubernetes-event-exporter将k8s的事件导出到elasticsearch日志系统中

    使用kubernetes-event-exporter将k8s的事件导出到elasticsearch日志系统中 前提 版本 kubernetes v1.17.9 kubernetes-event-ex ...

  8. 12_Python语法示例(函数)

    1.写一个函数mysum,此函数带有两个参数x,y打印出两个参数x,y的和 def mysum(x, y): print(x + y) mysum(3, 2) 2.写一个函数print_even,传入 ...

  9. 斗篷指令、属性指令、表单指令、条件指令、循环指令、js的Array操作、前台数据库、

    ```python"""1)指令 属性指令:v-bind 表达指令:v-model 条件指令:v-show v-if 循环指令:v-for 斗篷指令:v-cloak 2) ...

  10. 【Java/DateTime】用当前日期时间与确定日期时间比较,大于则执行某动作

    代码: package logbackCfg; import java.text.ParseException; import java.text.SimpleDateFormat; import j ...