关于线程的启动正确姿势

导语

在面试中,很多童鞋可能被问到过start方法和run方法的区别,貌似这种问题都被问烂了,笔者貌似也被问过,不记得当初是怎么回答的了,但肯定回答的怎么样。那么对于这样的问题,我们应该怎么回答呢?

当然首先肯定要从理论层面说一下,然后从代码层面说一下,但是理论还是要借助于代码实现来衬托的,所以本篇文章大体思路应该是先从代码实现上比较一下,然后对每个方法的基本实现进行一个简单的阐述。注:本篇文章只基于java代码实现来说,不基于JVM源码。

一、start方法与run方法的比较

对于start方法和run方法的比较,我们还是先从代码上来看,如下:

public class StartAndRunThreadDemo {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName());
        };
        runnable.run();
        new Thread(runnable).start();
//        Thread thread = new Thread(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println(Thread.currentThread().getName() + " invoked...");
//            }
//        });
//        thread.start();
//        thread.run();
    }
}

上面代码的执行结果如下:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" ...
main
Thread-0

从执行结果上,我们可以得知runnable.run()方法的执行结果是表示它是main线程所执行的,而start方法是由子线程去执行的,可是run()方法由主线程所执行的,这便不符合多线程环境下所具有的意义了。那我们的本意是什么呢?我们的本意是要新建一个线程然后把它启动起来。其实就是因为run方法它只是一个普通的java方法,它并不具有启动线程的作用。为什么这么说呢?我们来看下官网是如何阐述的:

上面的意思用简单的话来说就是:如果是直接调用Thread类中的run方法,而不不覆写该方法,那将没有上面实质的结果,虽然Thread类中的run方法实现了Runable接口,但是其本身并不具有启动线程的意义,这和Runable接口中的run方法没有什么实质的区别。对此我们后面还将进行说明。

二、start方法的(执行)原理

 

1.start方法的含义:

(1)线程的启动

首先来说start方法的含义是具有启动线程的作用的,而这里所说的线程便是我们常说的主线程,对此我们依旧以官网的解释为主,如下:

大体意思就是:当先处对象初始化后执行start方法,然后当前线程(主线程)去通知JVM虚拟机,如果虚拟机有时间的的话来执行新的线程,也就是Java虚拟机调用这个线程的run方法,此时的结果便是两个线程同时运行:当前线程(主线程)(从调用返回到start方法)和另一个线程(子线程)(执行其run方法)。

从上面我们知道,启动一个线程的本质就是通知JVM来运行一个线程,对于这个线程何时运行,就需要线程调度器去决定了,这个意思也就是说嗲用start方法的顺序并不能决定线程的运行顺序。

(2)具体准备工作

上面提到了另一个线程,就是子线程,后面统一用子线程来说明,但是子线程的运行时需要一些准备的,譬如汽车起动前需要:加满汽油、加满机油、保证蓄电池电量充足、启动开关没坏等等。线程运行前的准备也是如此,因此首先是需要使其处于就绪状态,就绪状态的意思是获得到了CPU以外的其他资源,譬如设置上下文、栈、线程状态以及寄存器,寄存器指向了程序的运行位置。当昨晚完以上操作后,子线程才能进一步被JVM调度到执行状态,调度到执行状态后,如果获得到了CPU资源,此时才会真正的进入到运行状态。其实这也可以看做是你开着一辆车行驶在马路上,你启动了汽车不代表你就可以跑了,而是要依赖于马路上的红绿灯。红绿灯就相当于JVM的调度作用了。

(3)两次调用start方法的结果

上面的官网截图中说:多次启动一个线程是不合法的。特别是,线程在完成执行后可能不会重新启动。意思就是说不可重复执行start方法。对此就不做代码演示了,感兴趣的童鞋可以自己去试试。但是我们会在下面的源码讲述中进行简单的说明。

2.start源码解析

关于start方法代码的执行可以简单分为以下几个步骤:

  • 启动时检查线程状态
  • 添加到线程组
  • 调用本地方法start0

那么我们还是来看下代码中是如何实现的吧,如下:

    public synchronized void start() {

        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

从上面的代码中,可以看出其大体执行步骤的确如我们上面所说的,首先是建厂线程状态,这也就是我们前面所说的重复执行start方法时,便会在此进行校验。然后便是添加线程组,我们也来看一下此步操作的代码:

    void add(Thread t) {
        synchronized (this) {
            if (destroyed) {
                throw new IllegalThreadStateException();
            }
            if (threads == null) {
                threads = new Thread[4];
            } else if (nthreads == threads.length) {
                threads = Arrays.copyOf(threads, nthreads * 2);
            }
            threads[nthreads] = t;
            nthreads++;
            nUnstartedThreads--;
        }
    }

其实此时我们应该考虑这样有个问题,我们只调用start方法,那么我们在哪里运行线程呢?那我们继续看下start0()方法的实现:

    private native void start0();

此时你会不会很失望,native方法便是虚拟机内的方法了,前面我们说了本篇文章不涉及JVM源码,所以这里就不做详细的代码展示了。

三、run方法原理

run方法的代码我们前面的文章已经做了一些展示,也就那么几行,这里我们还是贴一下代码吧,如下:

    public void run() {
        if (target != null) {
            target.run();
        }
    }

其实我们前面也说了为什么我们启动一个线程必须嗲用start方法,而不是run方法,这里从代码中我们也可以看出,run方法就是个普通的java方法,并没有实质性的操作系统的意义,而调用start方法是要调用jvm源码的,在jvm源码中会调用其相应的run方法的。这里就不赘述了。

 

总结

本篇文章进对run方法和start方法进行了简单的对比,其意义是要体现启动线程的正确姿势。我们文章的大体脉络如下:

  • start方法与run方法的比较
  • start方法的原理
  • run方法的原理

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值