实现多线程的方法是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. 并不是靠工作年限,有的人工作了5年技术却还是只懂皮毛。
    2. 要有强大的责任心,不放过任何bug,找到原因并去解决,这就是提高。
    3. 主动:永远不会觉得自己的时间多余,重构、优化、学习、总结等。
    4. 敢于承担:虽然这个技术难题以前没碰到过,但是在一定的了解调研后,敢于承担技术难题,让工作充满挑战,这一次次攻克难关的过程中,进步是飞快的。
    5. 关心产品,关心业务,而不只是写代码。
  • 微观上
    1. 看经典书籍
    2. 看官方文档
    3. 英文搜google和Stack Overflow
    4. 自己动手写,实践写demo,尝试用到项目里
    5. 不理解的参考该领域多个书本,综合判断
    6. 学习开源项目,分析源码

2.如何了解技术领域的最新动态

  • 高质量固定途径:ohmyrss.com(信息筛选,为我所用)
  • 订阅技术网站的邮件:InfoQ(每周都看)
  • 公众号不推荐作为技术知识来源,质量无法保证

3.如何在业务开发总成长

  • 偏业务方向
  • 偏技术方向
  • 两个25%理论

面试问题

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并发核心知识体系精讲》

发布了112 篇原创文章 · 获赞 303 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36221788/article/details/102598250