实现多线程的方法是 1 种还是 2 种还是 4 种?


网上和书籍的各种说法:鱼龙混杂

  • 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类是不推荐的,因为它有以下一些缺点:

  1. 从代码架构角度:具体的任务(run方法)应该和“创建和运行线程的机制(Thread类)”解耦,用runnable对象可以实现解耦。
  2. 使用继承Thread的方式的话,那么每次想创建一个新任务,只能新建一个独立的线程,而这样做的损耗会比较大(比如重头开始创建一个线程,执行完毕以后再销毁等。如果线程的实际工作内容,就是run方法里面只是简单的打印一行字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减少这样的损耗
  3. 继承Thread类后,由于Java语言不支持双继承,这样就无法再继承其他的类,限制了可扩展性
  • 两种方法的本质对比
    方法一和方法二,也就是“实现Runnable接口并实现run方法”和“继承Thread类然后重写run方法”在实现多线程的本质上并没有区别,最终都是调用了start()方法来启动线程。这两个方法的最主要区别在于run方法的内容来源
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
  1. 方法一:最终调用target.run()
  2. 方法二: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.有多少种实现线程的方法?思路有五点

  • 从不同的角度看,会有不同的答案
  • 典型答案是两种
  • 我们看原理,两种本质都是一样的
  • 具体展开说其他方式
  • 结论

答案示例:

  1. 从不同的季度看,会有不同的答案。
  2. 典型答案是两种,分别是实现Runnable接口和继承Thread类,然后具体展开说。
  3. 但是,我们看原理,其实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并发核心知识体系精讲》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

✦昨夜星辰✦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值