Java 多线程启动为什么调用 start() 方法而不是 run() 方法?

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java 多线程启动为什么调用 start() 方法而不是 run() 方法?

点击上方 **好好学java **,选择 **星标 **公众号


重磅资讯、干货,第一时间送达
今日推荐:终于放弃了单调的swagger-ui了,选择了这款神器—knife4j个人原创100W+访问量博客:点击前往,查看更多

多线程在工作中多多少少会用到,启动多线程调用的是 start() 方法,而不是 run() 方法,这是为什么呢?

在探讨这个问题之前,先来了解(复习)一些多线程的基础知识~

线程的状态

Java 中,定义了 6 种线程状态,在 Thread 类可以找到:


// 为了节约空间,我删除了注释
public enum State {
       NEW,//初始状态
       RUNNABLE,//运行状态
       BLOCKED,// 阻塞状态
       WAITING,//等待状态
       TIMED_WAITING,//超时等待状态
       TERMINATED;//终止状态
 }

这 6 种状态之间的关联,可以看下面这张图:

附上我历时三个月总结的 Java面试思维导图,拿去不谢!

下载方式

1. 首先扫描下方二维码

2. 后台回复「思维导图」即可获取

这张图描述的还是非常详细的,结合这张图,来说说这几种状态分别代表着什么意思:

  • 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() 方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态(NEW —> RUNNABLE)。具体什么时候执行,取决于 CPU ,由 CPU 统一调度。

我们又知道 Java 是跨平台的,可以在不同系统上运行,每个系统的 CPU 调度算法不一样,所以就需要做不同的处理,这件事情就只能交给 JVM 来实现了,start0() 方法自然就表标记成了 native。

最后,总结一下,Java 中实现真正的多线程是 start 中的 start0() 方法,run() 方法只是一个普通的方法。

最后,再附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,这是本人这几年及春招的总结,目前,已经拿到了大厂offer,拿去不谢!

下载方式

1. 首先扫描下方二维码

2. 后台回复「Java面试」即可获取

原文地址:https://sihai.blog.csdn.net/article/details/109465619

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java 多线程启动为什么调用 start() 方法而不是 run() 方法?


 上一篇
百度 71个炸天的开源项目,你知道几个? 百度 71个炸天的开源项目,你知道几个?
点击上方 **好好学java **,选择 **星标 **公众号 重磅资讯、干货,第一时间送达 今日推荐:终于放弃了单调的swagger-ui了,选择了这款神器—knife4j个人原创100W+访问量博客:点击前往,查看更多 项目
2021-04-04
下一篇 
volatile 手摸手带你解析 volatile 手摸手带你解析
点击上方 **好好学java **,选择 **星标 **公众号 重磅资讯、干货,第一时间送达 今日推荐:终于放弃了单调的swagger-ui了,选择了这款神器—knife4j个人原创100W+访问量博客:点击前往,查看更多 前言vola
2021-04-04