欢迎大家关注我的公众号"小猴子的技术笔记",有问题可以及时和我交流。

    由于Java是支持单继承的(接口除外),所以我们普遍启动线程的方式都是实现Runnable接口并重写run()方法。先来看下面一个简单的实例:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        try {
            // 睡眠3秒
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("实现Runnable接口的线程");
    }
}
public class MyRunnableTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("--------开始调用线程--------");
        Thread thread= new Thread(new MyRunnable());
        thread.run();
        System.out.println("--------调用线程结束--------");
    }
}

    你能猜出上面执行的顺序吗?或许你可以试着尝试用机器运行一下,把得到的结果和你的猜想进行一个验证。

    运行之前请仔细思考一下,如果run()方法启动了一个线程的话,那么在“MyRunnable”中我们设置了休眠,主线程应该继续往下执行,那么输出的结果应该就是:

--------开始调用线程--------
--------调用线程结束--------
实现Runnable接口的线程

    但是,我们来看下实际的运行结果:

--------开始调用线程--------
实现Runnable接口的线程
--------调用线程结束--------

    很显然run()方法其实是按照顺序执行的,并没有真正的启动一个线程。在解释原因之前我们先来看看下面这段代码:

Thread thread= new Thread(new MyRunnable());

    这段代码也就是构造了一个thread对象,并且把我们实现的Runnable传递了进去。我们点进源码查看一下这个构造方法。

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

    在这里,先不需要了解构造方法的每一参数,只需要注意一下我们传递进来的“MyRunnable”它是一个Runnable对象,这里赋给了"target"。请先记住这一点,他在后面的解析中很重要。
重点来了:接下来我们再来看看所谓的线程启动的代码:

thread.run();

    我们还是点击进去源码,能看到如下的代码示例:

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

    我想看到“@Override”这个关键词,大家应该立刻想到他是一个方法的重写,然后我们发现,其实Thread类也是实现了“Runnable”接口的。这里看到“target ”其实就是我们自定义的MyRunnable,“target.run()”最终调用的方法也就是“MyRunnable.run()”。因此可以得出一个结论,run()方法并不会真正的启动一个线程,调用run()方法,就相当于调用了一个普通方法,程序还是按照顺序执行的。下图展示了调用的过程。
image.png
    然后我们尝试正确的启动一个线程,代码如下:

public class MyRunnableTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("--------开始调用线程--------");
        Thread thread= new Thread(new MyRunnable());
        thread.start();
        System.out.println("--------调用线程结束--------");
    }

}

    运行结果:

--------开始调用线程--------
--------调用线程结束--------
实现Runnable接口的线程

    通过运行程序可以发现,这次的输入内容是符合我们之前的预期的。我们再次点进“start()”的源码进行查看(不用每一行都了解其原理,先大致了解即可。):


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) {
            }
        }

    这里比较重要的一个方法就是“start0()”跟进“start0()”的源码能够发现这个方法调用的是一个本地的方法,也就是交给底层去实现了。

private native void start0();

image.png

Thread.start()--->start0()--->run()

    总的来说我们启动一个线程的话,构造好线程之后,调用“start()”方法才算真正的启动了一个线程。

       欢迎大家关注我的公众号"小猴子的技术笔记",有问题可以及时和我交流。


小猴子的技术笔记
15 声望1 粉丝