开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
Java并发01:实现多线程的正确姿势
学习MOOC视频记录的笔记
1.网上的说法
2、4...
2.正确的说法
实现多线程的官方正确方法:2种
Oracle官网的文档是如何写的?
有两种方法可以创建新的执行线程。一种是将类声明为Thread的子类。该子类应该重写类的run方法,然后可以分配和启动子类的实例。
创建线程的另一种方法是声明一个实现Runnable接口的类。该类实现run方法,然后可以分配类的实例,在创建时作为参数传递给Thread并启动。
- 方法一:实现
Runnable接口 - 方法二:继承
Thread类
两种方法的对比:方法 1 (实现
Runnable接口)更好
- 代码架构角度:run方法中的内容对应任务,应该与线程解耦,将
run()方法这个行为独立出来,而不是放在Thread内部 [策略模式???]- 节约资源,通过Thread类新建一个独立线程的损耗较大(创建,执行,销毁)
- 无法继承其他类了
两种方法的本质对比
方法一:最终调用 target.run();
方法二:run()整个都被重写
先查看 Thread 类中的 run() 方法:
private Runnable target;
......
@Override
public void run() {
if (target != null) {
target.run();
}
}
再查看Runnable 接口里面的 run() 方法
public interface Runnable {
public abstract void run();
}
思考题:同时用两种方法会怎么样?
由于target != null,因此会调用Runnable接口里面的方法。
Thread直接把run()方法给完全覆盖重写了
从面向对象的思想去考虑
public class BothRunnableThread {
public static void main(String[] args) {
// 创建了一个匿名内部类,传入了一个Runnable对象,并重写了run()方法
// 最后输出 ---> 我来自Thread
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我来自Runnable");
}
}){
@Override
public void run() {
System.out.println("我来自Thread");
}
}.start();
}
}
总结:最精准的描述
-
通常我们可以分为两类,
Oracle官方文档也是这么说的(第一类是继承Thread类,第二类是实现Runnable接口) -
准确的讲,创建线程只有一种方式,那就是构造
Thread类,而实现线程的执行单元有两种方式- 方法一:实现
Runnable接口的run方法,并把Runnable实例传给Thread类 - 方法二:重写
Thread的run方法(继承Thread类)
- 方法一:实现
3.经典错误观点
-
线程池创建线程也算是一种新建线程的方式
事实上最后会有一个
ThreadFactory来创建线程,而默认的线程池里面的线程也是通过上述方式创建的,还是使用的new Thread,可以通过源码看出来
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
-
通过
Callable和FutureTask创建线程,也算是一种新建线程的方式通过类的结构图可以看出
FutureTask实现了RunnableFuture接口,而RunnableFuture也实现了Runnable接口,本质上也离不开Runnable接口
-
无返回值是实现
runnable接口,有返回值是实现callable接口,所以callable是新的实现线程的方式同上,本质上还是实现的
runnable接口 -
定时器
-
匿名内部类
-
Lambda表达式
多线程的实现方式,在代码中写法干变万化,但其本质万变不离其宗。
4.彩蛋:如何从宏观和微观两个方面来提高技术?如何了解技术领域的前沿动态?如何在业务开发中成长?
经典知识
宏观上
- 并不是靠工作年限,有的人工作了
5年技术却还是只懂皮毛。 - 要有强大的责任心,不放过任何
bug,找到原因并去解决,这就是提高。 - 主动:永远不会觉得自己的时间多余,重构、优化、学习、总结等。
- 敢于承担:虽然这个技术难题以前没碰到过,但是在一定的了解调研后,敢于承担技术难题,让工作充满挑战,这一次次攻克难关的过程中,进步是飞速的。
- 关心产品,关心业务,而不只是写代码。
微观上
- 看经典书籍(指外国人写的经典的中国译本,比如说 Java并发编程实战、自顶向下计算机网络)
- 看官方文档
如果前两点不能解决问题:
- 英文搜
google和stackoverflow - 自己动手写,实践写
demo,尝试用到项目里 - 不理解的参考该领域的多个书本,综合判断
- 学习开源项目,分析源码(学习
synchronized原理,反编译看cpp代码)
技术领域的最新动态
- 高质量固定途径:
ohmyrss.com(信息源筛选,为我所用) - 订阅技术网址的邮件:
InfoQ(每周都看)
彩蛋:如何在业务开发中成长
- 偏业务方向:eg: 电商、搜索、出行;了解业务的核心模型(整理,关键表,关键字段,积累),复杂业务合理抽象;考虑远一些,预留处理,快速扩展
- 偏技术方向:eg: 中间件,存储系统,广泛的适用场景,通用性强
- 两个
25%理论
5.常见面试问题
有多少种实现线程的方法?思路有 5 点
- 从不同的角度看,会有不同的答案
- 典型答案是两种 (哪个好哪个不好,3点优势)
- 我们看原理,两种本质都是一样的 (run方法区别,重写run方法,target执行run方法)
- 具体展开说其他方式 (线程池,计时器…)
- 结论
实现
Runnable接口和继承Thread类哪种方式更好?
- 从代码架构角度 【解耦,具体的任务】
- 新建线程的损耗 【可以反复利用这同一个线程,eg: 线程池】
Java不支持双继承