由于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()方法,就相当于调用了一个普通方法,程序还是按照顺序执行的。下图展示了调用的过程。
然后我们尝试正确的启动一个线程,代码如下:
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();
Thread.start()--->start0()--->run()
总的来说我们启动一个线程的话,构造好线程之后,调用“start()”方法才算真正的启动了一个线程。