1、为何说只有 1 种实现线程的方法?

发布时间 2023-10-09 10:09:32作者: 软柠柠吖

1、为何说只有 1 种实现线程的方法?

为什么说本质只有一种实现线程的方式?

实现 Runnable 接口究竟比继承 Thread 类实现线程好在哪里?

实现多线程的多种方式

1、通过实现 Runnable 接口的方式实现多线程

  • 首先通过 CustomRunnableThread 类实现 Runnable 接口
  • 然后实现了 run() 方法
  • 之后只要把这个实现了 run() 方法的实例传到 Thread 类中去就可以实现多线程

image-20231008213424779.

2、继承 Thread 类

  • 与第一种方法不同的是它没有实现接口,而是继承 Thread 类,并重写了其中的 run() 方法

image-20231008213654053.

3、通过线程池创建线程

  • 默认是采用 DefaultThreadFactory
  • 它会给我们线程池创建的线程设置一些默认的值,比如它的名字,它是不是守护线程,以及它的优先级

image-20231008214811236.

4、有返回值的 Callable 也是一种新建线程的方式

  • 实现了 Callable 接口,并且给它的泛型设置成 Integer,然后它会返回一个随机数回来

image-20231008215906888.

5、其他创建方式,

  1. 定时器 Timer

    • TimerTask 实现了 Runnable 接口,Timer 内部有个 TimerThread,它继承自 Thread,因此绕回来还是 Thread

    image-20231008220500658.

    image-20231008220535897.

  2. 匿名内部类

    • 它不是传入一个已经实例好的 Runnable 对象,而是直接用一个匿名内部类的方式把需要传入的 Runnable 给实现出来

    image-20231008221713029.

  3. Lambda 表达式

    • 匿名内部类和 Lambda 表达式创建线程仅仅是表面层次的,最终它们依然符合最开始所说的那两种实现线程的方式(实现 Runnable 接口或是继承 Thread 类)

    image-20231008221742589.

实现线程本质上只有一种方式

透过现象看本质,以上最终实现线程的方式都是实现 Runnable 接口或是继承 Thread 类,但为什么说这两种方式本质上还是一种呢?

启动线程需要调用 start() 方法,而 start() 方法最终还会调用 run() 方法来执行具体业务功能

实现 Runnable 接口或是继承 Thread 类这两个方式的最主要区别在于 run() 方法的内容来源:

  • 方式一:实现 Runnable 接口

    • 最终调用 target.run()

    image-20231009081030550.

  • 方式二:继承 Thread 类

    • run() 整个都被重写,重写之后的 run() 方法直接就是所需要执行的任务,最终还是需要调用 thread.start() 方法来启动线程,而 start() 最终也会调用到这个被重写的 run() 方法,来执行任务

创建线程就只有一种方式:构造 Thread 类

实现线程 "运行内容" 的两种方式

  1. 实现 Runnable 接口的 run() 方法,并把 Runnable 实例作为 target 对象,传给 Thread 类,最终调用 target.run()
  2. 继承 Thread 类,重写 Thread 的 run() 方法,thread.start() 会执行 run()

至于其他实现线程 "运行内容" 的方式(线程池、Timer等)都是对上面 2 种方式的进一步封装

软柠柠吖思考:为什么说本质只有一种实现线程的方式?

答:创建线程就只有一种方式,即构造 Thread 类。但是实现线程的 "运行内容" 却有多种方式,但是都是基于实现 Runnable 接口或者继承 Thread 类,来实现或者重写 run() 方法,最终交由 thread.start() 来执行 run() 方法中的任务。

Runnable 接口与继承 Thread 类对比

实现 Runnable 接口要比继承 Thread 类更好

实现 Runnable 接口的 3 个好处:

  1. 可以把不同的内容进行解耦,权责分明(从代码架构考虑,实际上,Runnable 接口里面只有一个 run() 方法,它定义了需要执行的内容,这种情况下,实现了 Runnable 与 Thread 的解耦,Thread 负责线程的启动,属性设置等内容,它们权责分明)
  2. 某些情况下可以提升性能,减少开销(使用继承 Thread 类的方式每次执行一次任务都需要新建一个独立的线程,执行完之后,线程走到生命周期的尽头被销毁。如果还想执行这个任务,就必须再新建一个继承 Thread 类的线程,这个时候如果执行的内容比较少,比如说只是在 run() 方法中简单的打印一行文字,那么它所带来的开销本身并不大。相比于整个线程从创建到执行完毕被销毁这一系列操作,比 run() 方法中打印一行文字本身的开销还要大的多,属实是捡了芝麻,丢了西瓜,得不偿失。如果使用实现 Runnable 接口的方式,则可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次都线程和销毁线程,大大降低了性能开销
  3. 继承 Thread 类相当于限制了代码未来的可扩展性(Java 语言本身不支持双继承,如果类一旦继承 Thread 类,后续就无法继承其他的类,这样一来,如果这个类未来需要继承其他类实现功能上的扩展,它就没有办法做到了,限制了代码未来的可扩展性)

总是所述:优先选择通过实现 Runnable 接口的方式来创建线程