1. 实现多线程的正确姿势
此笔记使用wolai进行梳理,欢迎来访,指出问题 1核心一:实现多线程的正确姿势 www.wolai.com/mucong/jiGM…
1.1究竟如何创建新线程
网上说法:1种,2种,3种、、、、
- 说法不统一,那么究竟是多少种呢?
Oracle官方解答:2种
-
实现Runnable接口创建线程
-
继承Thread创建线程
思考与总结:
为什么是两种方法而不是更多,通过一下五个方面进行阐述
-
阐明叙事角度:从不同的角度看,会有不同的答案。
-
两种方法实现,分别是实现Runnable接口和继承Thread类
-
我们看原理,其实Thread类实现了Runnable接口,并且看Thread类的run方法,会发现其实那两种本质都是一样的,run方法的代码如下
-
方法一和方法二,也就是“继承Thread类然后重写run()”和“实现Runnable接口并传入Thread类”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法的最主要区别在于run()方法的内容来源: 方法一:最终调用target.run(); 方法二:run()整个都被重写
-
还有其他的实现线程的方法,例如线程池等,它们也能新建线程,但是细看源码,从没有逃出过本质,也就是实现Runnable接口和继承Thread类
@Override public void run() {
if (target != null) {
target.run();
}
}
结论:我们只能通过新建Thread类这一种方式来创建线程,但是类里面的run方法有两种方式来实现,第一种是重写run方法,第二种实现Runnable接口的run方法,然后再把该Runnable实例传给Thread类。除此之外,从表面上看线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不出刚才所说的范围。
思考:二者的使用优先级(当然是Runnable啦
- 从三个角度分析
-
从代码架构方面解释:具体的任务(run方法)应该与"创建和运行线程的机制(Thread类)”解耦,用runnable对象可以实现解耦合
-
如果使用继承,我们每次新建一个任务,都要独立的新建一个线程,消耗较大(比如冲头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。而使用Runnable和线程池,就可以大大减小这样的损耗
-
由于Java是单继承多实现的,继承少用
1.2两种方式同时使用会怎么样?
- 代码如下
/**
* Created by mucong on 2021/1/10 16:30
* 描述: 同时使用Runnable与Thread方法创建线程会怎么样
*/
public class BothRunnableThread {
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与Runnable同时创建线程并调用run方法会丢失Runnable的run方法
-
原因:由1.1中提到,创建线程就只有两种方法,而使用Thread的run方法相当于覆盖了父类的run方法,结果会导致Runnable无法调用底层run方法的内容。
1.3典型错误观点剖析
还未完成整理
-
"线程池创建线程也算是一种新建线程的方式"
-
"通过Callable和FutureTask创建线程,也算是一种新的创建线程的方式"
-
"无返回值是实现runnable接口,有返回值是Callable接口,所以callable是新的实现线程的方式"
-
定时器
-
匿名内部类
-
Lambda表达式
1.4常见面试题
有多少种实现线程的方法?
实现Runnable接口与继承Thread类哪种方式更好?