创建线程的方式到底有几种?
可能你的答案是 2 种、3 种或者是 4 种,但是本质上来说创建线程只有一种方式。
实现 Runnable 接口
public class RunnableThread implements Runnable { |
第 1 种方式是通过实现 Runnable
接口创建线程。如代码所示,RunnableThread
类实现
Runnable
接口,然后重写 run()
方法,之后只需要把这个实现了 run()
方法的实例传到
Thread
类中就可以创建线程。
继承 Thread 类
public class ExtendsThread extends Thread { |
第 2 种方式是继承 Thread
类,如代码所示,ExtendsThread
类继承 Thread
类,并重写了其中的 run()
方法。
线程池创建线程
/** |
对于线程池而言,本质上是通过线程工厂创建线程的,默认采用的工厂是
DefaultThreadFactory
,它会给线程池创建的线程设置一些默认值,如线程名、是否是守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终还是通过
new Thread()
创建线程
,只不过这里的构造函数传入的参数要多一些。
由此可以看出通过线程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还是通过
new Thread()
实现的。
实现有返回值的 Callable 接口
static class CallableTask implements Callable<Integer> { |
第 4 种线程创建方式是通过实现有返回值的 Callable
接口创建线程,Runnable
创建线程是无返回值的,而
Callable
和与之相关的
Future
、FutureTask
,它们可以把线程执行的结果作为返回值返回,如代码所示,CallableTask
类实现了 Callable
接口,实现其中的 call()
方法。
但是,无论是 Callable
还是
FutureTask
,它们首先和 Runnable
一样是一个任务,是需要被执行的,而不是说它们本身就是线程。它们可以放到线程池中执行,如代码所示,
submit()
方法把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的,而子线程的创建方式仍脱离不了最开始的两种基本方式,也就是实现
Runnable
接口和继承 Thread
类。
其他创建方式
1)定时器 Timer
,本质上定时器底层有一个继承自
Thread
类的 TimerThread
;
2)匿名内部类创建线程,这仅仅只是 Java 的语法糖。
创建线程只有一种方式
先说最基本的两种方式基于实现 Runnable
接口或继承
Thread
类实现,这两种方式本质上是一样的。
创建线程需要调用 start()
方法,而 start()
方法最终还会调用 run()
方法。
|
由此,创建线程只有一种方式,就是构造一个 Thread
类,这是创建线程的唯一方式。上说说的那么多,都只是在构造一个「线程体」,而不是线程,仅仅在于实现线程运行内容的不同。
推荐实现 Runnable 接口
1)从代码的架构考虑
Runnable
里只有一个 run()
方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable
与 Thread
类的解耦,Thread
类负责线程启动和属性设置等内容,权责分明。
2)在某些情况下可以提高性能
使用继承 Thread
类的方式,每次执行一次任务,都需要新建一个独立的线程,执行完任务后线程走到生命周期的尽头被销毁,如果还想执行这个任务,就必须再新建一个继承了
Thread
类的类,如果此时执行的内容比较少,比如只是在
run()
方法里输出一段日志,那么它所带来的开销并不大,相比于整个线程从开始创建到执行完毕被销毁,这一系列的操作比
run()
方法输出日志本身带来的开销要大得多。如果选择使用实现
Runnable
接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。
3)Java 不支持多继承
如果一个类一旦继承了 Thread
类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。
=> 优先选择通过实现 Runnable 接口的方式来创建线程。