赣州新闻网

首页 / 科技

【JAVA干货】为什么本质上只有一种实现线程的方式?

2020-03-30 · 赣州新闻网 · 科技


【JAVA干货】为什么本质上只有一种实现线程的方式?

本文作者:拉勾教育专栏作者 徐隆曦

你好,欢迎来到《Java 并发编程核心 78 讲》,我是徐隆曦,今天我们主要学习为什么说本质上只有一种实现线程的方式?实现 Runnable 接口究竟比继承 ThJ 0 G : / n g j Iread 类实现线程好在哪里?

【JAVA干货】为什么本质上只有一种实现线程的方式?

实现线程是并发编程中基K % y 0 u础中的基础,因为我们必须B - p要先实现多线程,才可以继续后续的一系列操作。所以本课B $ ( | p Z x时就先从并发编程的基础如何实现线程开始讲起,希望你能够夯实基础,虽然实现线程看似简单、基础,但实际上却暗藏玄机。首先,我们来看下为l E Y什么说本质上实现线程只有一种方式?

【JAVA干货】为什么本质上只有一种实现线程的方式?

实现线程的方式到底有几种?大部分人会说有 2 种、3p { q 种或是 4 种,很少有人会说有 1 种。我们接下来看看它们具体指什么~ g z m n @?2 种实现方式的描述是最基本的,也是最为大家熟知的,我们就先来看看 2 种线程n ; J O w /实现方式J R X ~ 0 = C Z ;的源码。

实现 Runnable 接口

【JAVA干货】为什么本质上只有一种实现线程的方式?

第 1 种方式是通过实现 Runnable 接口实现多线程,如代码所示,首先通过RunnableThread 类实现 Runnable 接口,然后重写 run() 方法,之后只需要把这个实现了 run() 方3 k : % ! z 6法的实例传到 Thread 类中就可以实现多线程。

继承 Thread 类

【JAVA干货】为什么本质上只有一种实现线程的方式?

第 2 种方式是继承 Thread 类,如代码所示,与第 1 种方式不同的是它没有实现接口,而是继承 Thread 类,并重写了其中的 run() 方法。相信上面这两种方式你一定非常熟悉,并且B W r经常在工作中使用它们。

线程池创建线程

【JAVA干货】为什么本质上只有一种实现线程的方式?

那么为什么说还有第 3 种或第 4 种方式呢B & b?我们先来看看第 3 种方式:通过线程池创建线程。线程池确实实现了多线程,比如我们给线程 I ] ) | @ } o 0池的线程数量设置成 10,那么就会有 10 个子 U B i [ S线程来为我们v 0 B工作,接下来,我们深入解析线程池中的源码,来看看线程池是怎么{ _ 4 F d实现线程的?

【JAVA干货】为什么本质上只有一种实现线程的方式?

对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFx & ~actory ,它会给线程池创建的线程设置一些默认值,比w w o如:线程的名字、是否是守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终它还是通过 new Thread() 创建线程的 ,只不过这里的构造函数传D ~ M J L ? d # c入的参数要多一些,由此可以看出通过线o W 程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还I 6 r是通过 new Thread() 实现的。

在面试中,如果你只是知道这种方式可以创建线程但不了解其背后的9 & w O C f i 8 P实现原理,就会在面试的过程中举步维艰,想更好的表现自己却给自己挖了“坑”。

所以我们在回答线程实现的问题时,描述完前两种方式,可以进一步引申说“我还知道线程池和Callable 也是可以创建线程的,但是它们本质上也是通过前两种基本方式实现的线程创建。”这样的回答会成为面x A 4 0首先和 RunnableE p & , f 一样,都是一个任务,是需要被执f S E , Y N # ]行的,而不是说它们本身就是线程。它们可以放到线程池中执行,如代码所示, submit() 方法P ? f把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的,而子线程的创建方式仍脱离不了最开始讲的两种基本方式,也就是W ! b实现 Runnable 接口和继承 Thread 类。

更多JAVA干货,见文末了解更多


其他创建方式


定时器 Timer

讲到这里你可能会说,我还知道一些其他的实现线程的方式。比如,定时: = 9 J # S K ^器也可以实现线程,如果新建一个 Timer,令其每隔 13 M % p ^ b z / R0 秒或设置两个小时之后,执行一些任务,那么这时C 8 - 3 h g它确实K A k K x , 3也创建了线程并执行了任务,b @ % g N K I但如果我们深入分析定时器的源码会发现,本质x M w y ) P } V上它还是会有一个继承自 Thread 类的 TimerThread,所以定时器创建线程最后又绕回到最开始说的两种方式。

其他方法

【JAVA干货】为什么本质上只有一种实现线程的方式?

或许你还会说,我还知道一些其他方式,比如匿名内部类或 lambda 表达式方式,实际上,匿名内部类或 lambda 表达式创建线程,它们仅仅是在语法层面上实现了线程,并不能把它归结于实现多线: = B程的方式,如匿名内部类实现线程的代码所示,它仅仅是用一k 1 @ r b p c K个匿名内部类把需要传入的 Runnable 给实例出来。

【JAVA干货】为什么本质上只有一种实现线程的方式?

我们再来看下 lambda 表达式方式。如代码所示,最终它们依_ P | -然符合最开始所说的那两种R S t 2ad 类实现线程要好?好在哪里呢?

首先,我们从代码的架构考虑,实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这e z K }种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。

第二点就是在某些情况下可以提高性能,使用继承 Thread 类方式U 2 : W e,每次执行一次任务,都需要新建一c $ } 个独立的线程,执行完任务后线程走到生命周期的尽头被销毁,如s - $ q }果还想执行这个任务4 ~ 9 z C,就必须再新建一个继承了 Thread 类的类,如果此时执行的内容比较少,比如只是在 run() 方法里简单打印一行文字,那么它所带来的开销并不大,相比于整8 x H个线程从开始创建到执行完毕被销毁,这一系列的操作比 run() 方法打印文字本身带来的开销要大得多,相当于捡了芝麻丢了西瓜,得不偿失。如果我们使用实现 RunnableX [ I + 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性q 0 V x { a i能开销。

第三点好处在于 Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。

综上所述,我们应该优先选择通过实现 Runnable 接口的方式来创建线程。

【JAVA干货】为什么本质上只有一种实现线程的方式?

好啦,本课时的全部内容就讲完了,在这一课时我们主要学习了 通过 Runnable 接口和继承 ThrD c & @ Oead 类等几种方式创建线程,又详细分析了为什么说本质上只有一种实现线程的方式,以及实现 Runnable 接口究竟比继承 Thread 类实现线程好在哪里?学习完本课时相信你一定对创建线程有了更深入的理解,我们下一课时将继续学习“R y = X l如何正确停止线程?为什么用 volatiR C [ 5 _le 标记位的停4 c _ ! R } l止方法是错误的?”下一课时见。


版权声明:本文版权归属拉勾教育及该专栏作者,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方K Q 5 Y 6 e式复制发布/发表6 ; c G a,违者必究。


原文:JAVA并发面试72讲 | 更多JAVA干货,见文i p l g i ^末了解更多

新建工作工厂接口设置内容瑞金方式线程