于是我打开Oracle的java官方文档去寻找答案,官方的答案是两种。
- 实现Runnable接口
public class ImplRunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("使用Runnable方式实现多线程");
}
public static void main(String[] args) {
new Thread(new ImplRunnableStyle()).start();
}
}
- 继承Thread类
public class ExtendsThreadStyle extends Thread {
@Override
public void run() {
System.out.println("使用继承Thread方式实现多线程");
}
public static void main(String[] args) {
new ExtendsThreadStyle().start();
}
}
在真正的编程环境中,推荐使用的方式还是实现Runnable接口的方式,原因这里有3点:
- 从架构角度来看,使用Runnable的方式可以将具体的任务(run方法)和创建、运行线程的机制(Thread类)解耦,代码结构更加优雅;
- 如果使用Thread继承的方式,那么每次想创建一个新的任务都必须创建一个新的独立线程,这样就会造成额外的资源损耗,如果使用Runnable和线程池就可以避免这样无谓的损耗;
- 由于Java语言不支持多继承,如果使用继承Thread的方式,就无法再继承其他的类,限制了可扩展性。
聊到这里我们是不是可以结束这个问题了?NO,NO,用电视剧里面比较拉仇恨的话讲,“就这?”
我们再来看一道程序执行逻辑的思考题:
public class BothImplRunnableAndExtendsThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("执行实现Runnable接口方式的代码块");
}
}) {
@Override
public void run() {
System.out.println("执行继承Thread方式的代码块");
}
}.start();
}
}
假如我们这两种方式一起使用,会执行哪个代码块?
咋一看这个题是不是感觉有点懵逼,正常开发的时候谁会这么写代码。。。但是我想说,如果你能不运行程序就知道这道题的答案,并且可以给出原因,那说明你对上面讲的两种实现线程的方式才有了真正的理解。
我先给出程序的执行结果:控制台会输出“执行继承Thread方式的代码块”,原因呢?我们去看一下Thread类run方法的源码就了解了:
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
代码很简单,如果target!=null就执行target.run()方法。那target又是什么?看了Thread类的源码应该很明白了吧,target就是我们传过去的实现Runnable接口的对象,那刚才那道思考题的答案为什么是“执行继承Thread方式的代码块”,小伙伴们也应该很清楚了。因为当使用继承Thread方式的时候,我们重写了run方法,那么Thread类中run方法的代码逻辑肯定就执行不了了。所以只会执行我们重写的run方法代码中的逻辑。
下面我们再来聊一下网上某些博客中的“观点”:
- “线程池创建线程也算是一种新建线程的方法”
public class ThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程池的方式创建线程");
}
});
}
}
上面是通过线程池创建线程的简单代码,国际惯例我们直接看线程池的源码是怎样创建线程的:网上还有很多其他的说法例如:“通过Callable和FutureTask创建线程”、“定时器创建线程”等等其实都是一种包装,用赵本山的话讲“你以为穿上马甲我就不认识你了?”
最后我们总结下:
我们通过新建Thread类的方式创建一个独立线程,但是类里面的run方法有两种方式来实现:第一种是重写Thread类的run方法;第二种是实现Runnable接口的run方法,然后再把该Runnable接口的实例传给Thread类。除此以外,所谓的线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不过Java官方文档提到的两种方式。