前言
看到这个标题的小伙伴先别着急喷我……在面试的时候,我们经常会被问到这种基础题:Java创建线程的方式有几种?
比较正常的答法当然是三种:
- 继承Thread
- 实现Runnable接口
- 实现callable接口
一般来说这种属于送分题,不过大家都是这样答的,好像有点一般般,有没有什么答法能够让面试官眼前一亮呢?当然有!
事实上,这个问题可以从两个角度去思考:
- Java层次
- 操作系统层次
大部分人的回答都是从Java提供的API层次,所以得出了三种,甚至还有加上线程池四种的结论,但你有没有想过,Java本身是不具备在操作系统上创建线程能力的。
更深入点思考:
Java提供的API分别是怎么创建线程的?
继承Thread方式
第一种方式,在构造方法实际上只是做了一些状态的初始化, 并没有涉及线程的创建,关键点其实是在Thread类的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方法,名字起得这么奇怪,可惜点进去会发现它是一个native方法,我们暂时先跟到这里。
实现Runnable接口
runnable是一个接口,代码十分简单:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
思考下我们平时怎么用Runnable的?是不是实现这个接口,重写run方法,然后扔到Thread类里去执行?实际上我们继承Thread类也是要重写run方法,OK,我们再看看第三个。
实现callable接口
callable的情况好像有点不一样,它没有我们熟悉的run方法了:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
然而真的没有吗?我们再来想想callable的用法,好像还有个FutureTask类没有出现,我们点进来就会发现,这个类居然是Runnable的实现类!
不用想了,直接看看run方法的逻辑,果然在这里有调用call()方法,那么是实际上我们还是可以认为是run方法的效果。
总结
从上面三个方式的分析,我们最终可以粗略得出来一个结论:
无论是继承Thread类还是实现接口,最终都是通过Thread类的start方法创建线程,由run方法执行逻辑
也就是说,对于Java提供的API来说,创建线程的方式实际上只有一种,也就是刚才得出来的结论。
到这里我们已经得到可以让面试官眼前一亮的答案了,那么这个native方法的具体逻辑是怎么实现的呢?这里我就不深入分析了,大家可以去找找相关博文,大概是创建线程后得到时间片时会由一个回调的处理来执行run方法的逻辑