文章目录
网上和书籍的各种说法:鱼龙混杂
- 1种观点
- 2种观点
- 4种观点
- 其他观点
实现多线程的官方正确方法:2种
- Oracle官网的文档是如何写的?
- 方法一:实现Runnable接口
- 方法二:继承Thread类
方法一:实现 Runnable 接口
public class RunnableStyle implements Runnable{
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
@Override
public void run() {
System.out.println("用Runnable方法实现线程");
}
}
方法二:继承 Thread 类
public class ThreadStyle extends Thread{
@Override
public void run() {
System.out.println("用Thread类实现线程");
}
public static void main(String[] args) {
new ThreadStyle().start();
}
}
两种方法的对比
- 方法一(实现Runnable接口)更好
继承Thread类是不推荐的,因为它有以下一些缺点:
- 从代码架构角度:
具体的任务(run方法)应该和“创建和运行线程的机制(Thread类)”解耦
,用runnable对象可以实现解耦。 - 使用继承Thread的方式的话,那么每次想创建一个新任务,只能新建一个独立的线程,而
这样做的损耗会比较大
(比如重头开始创建一个线程,执行完毕以后再销毁等。如果线程的实际工作内容,就是run方法里面只是简单的打印一行字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减少这样的损耗
。 - 继承Thread类后,由于Java语言不支持双继承,这样就无法再继承其他的类,
限制了可扩展性
。
- 两种方法的本质对比
方法一和方法二,也就是“实现Runnable接口并实现run方法”和“继承Thread类然后重写run方法”在实现多线程的本质上并没有区别
,最终都是调用了start()方法来启动线程。这两个方法的最主要区别在于run方法的内容来源
:
@Override
public void run() {
if (target != null) {
target.run();
}
}
- 方法一:最终调用target.run()
- 方法二:run()整个都被重写了
思考:同时用两种方法会怎么样?
public class BothRunnableThread {
public static void main(String[] args) {
new Thread(
() -> System.out.println("我来自Runnable")
) {
@Override
public void run() {
System.out.println("我来自Thread");
}
}.start();
}
}
执行结果:
我来自Thread
从面向对象的思想去考虑,其实就是Runnable方式run()被实现,但是实现又被Thread方式run()方法给覆盖了,所以只打印了Thread方式run()方法里面的内容。
总结:最精准的描述
- 通常我们可以分为两类,Oracle也是这么说的
- 准确的讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式
- 方法一:实现Runnable接口的run方法,并把Runnable实例传给Thread类
- 方法二:重写Thread的run方法(继承Thread类)
典型错误观点
1.线程池创建线程也算是一种新建线程的方式
2.通过Callable和FutureTask创建线程,也算是一种新建线程的方式
3.无返回值是实现runnable接口,有返回值是实现callable接口,所以callable是新的实现线程的方法
4.定时器
5.匿名内部类
6.Lambda表达式
多线程的实现方式,在代码中写法千变万化,但其本质
万变不离其宗
。
面试问题
1.有多少种实现线程的方法?思路有五点
- 从不同的角度看,会有不同的答案
- 典型答案是两种
- 我们看原理,两种本质都是一样的
- 具体展开说其他方式
- 结论
答案示例:
- 从不同的季度看,会有不同的答案。
- 典型答案是两种,分别是实现Runnable接口和继承Thread类,然后具体展开说。
- 但是,我们看原理,其实Thread实现了Runnable接口,并且看Thread类的run方法,会发现其实那两种本质都是一样的,run方法的代码如下:
@Override
public void run() {
if (target != null) {
target.run();
}
}
方法一和方法二,也就是“继承Thread类然后重写run”和“实现Runnable接口并传入Thread类”在实现多线程的本质上,并没有区别,都是最终调用start方法来新建线程。这两个方法最主要的区别在于run方法的内容来源:
方法一:最终调用target.run()
方法二:run()整个都被重写
4. 然后具体展开说其他方式,还有其他的实现线程的方法,例如线程池等,它们也能新建线程,但是细看源码,从没逃出过本质,也就是实现Runnable接口和继承Thread类。
5. 结论:我们只能通过新建Thread类这一方式来创建线程,但是类里面的run方法有两种方式来实现,第一种是重写run方法,第二种是实现Runnable接口的run方法,然后把该runnable实例传给Thread类。除此之外,从表面上看,线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不过刚才所说的范围。
2.实现Runnable接口和继承Thread类哪种方式更好?
- 从代码架构角度
- 新建线程的损耗
- Java不支持多继承
笔记来源:慕课网悟空老师视频《Java并发核心知识体系精讲》