一、实现多线程到底有多少种方法
实现Java多线程,到底有多少种方法?
Oracle官网:两种
方法一:实现Runnale
接口
/**
* 描述:用Runnable方式创建线程
*
* @author Juning
* @date 2019-08-29 23:41
*/
public class RunnaleStyle implements Runnable {
public static void main(String[] args) {
Thread thread = new Thread(new RunnaleStyle());
thread.start();
}
@Override
public void run() {
System.out.println("用Runnable方法实现线程");
}
}
方法二:继承Thread
类
/**
* 描述:用Thread方式实现线程
*
* @author Juning
* @date 2019/8/29 23:47
*/
public class ThreadStyle extends Thread {
@Override
public void run() {
System.out.println("用Thread方式实现线程");
}
public static void main(String[] args) {
new ThreadStyle().run();
}
}
两种方法的对比
Oracle官方文档上说:
Thread
类有两个常用的构造器(构造函数)
- 方法一:实现
Runnale
接口需要一个Runnale
参数- 方法二:继承
Thread
类是不需要任何参数的 那么那种方法更好呢?它们的区别在哪里呢?
方法一(实现Runnale接口)
实现Runnale
接口好在哪里?方法二的缺点主要有三个:
-
解耦 从代码架构角度:具体的任务(
run()
方法)应该和“创建和运行线程的机制(Thread
类)”解耦,用Runnable
对象可以实现解耦。 -
资源节约 使用继承
Thread
的方式的话,那么每次想新建一个任务,只能新建一个独立的线程,而这样做的损耗会比较大(比如重头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是run()
函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable
和线程池,就可以大大减小这样的损耗。 -
拓展性不足 继承
Thread
类以后,由于Java语言不支持双继承,这样就无法再继承其他的类,限制了可扩展性。
两种方法的本质对比
这两种方法本质上的区别又是什么呢?
- 方法一:最终调用
target.run()
; - 方法二:
run()
整个都被重写
方法一和方法二,也就是“实现Runnable
接口并传入Thread
类”和“继承Thread
类然后重写run()
”在实现多线程的本质上,并没有区别,都是最终调用了start()
方法来新建线程。
这两个方法的最主要区别在于run()
方法的内容来源:
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
二、同时使用两种方法会怎么样
废话不多说,直接上代码:
/**
* 描述:同时使用Runnable和Thread两种实现线程的方式
*
* @author Juning
* @date 2019/9/16 23:45
*/
public class BothRunnableThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我来自Runnable");
}
}) {
@Override
public void run() {
System.out.println("我来自Thread");
}
}.start();
}
}
结果如下:
从我们的运行结果来看,由于Java
语言的特性,虽然new Thread
传入了一个Runnable
对象,但是当我们重写run()
方法的时候,就将原本需要Runnable
对象的的run()
方法覆盖掉了,所以会出现只有“我来自Thread”的输出结果。
三、最精准的描述
根据以上的探究,在别人问我们实现多线程到底有多少种方法的时候我们可以这样说
- 通常我们可以分为两种方法,
Oracle
它也是这么说。 - 准确的将,创建线程的方式只有一种方法,那就是构造
Thread
类,而实现线程的执行单元有两种方式:- 方法一:实现
Runnable
接口的run()
方法,并把Runnable
的实例传给Thread
类 - 方法二:重写
Thread
的run()
方法(继承Thread
类)
- 方法一:实现
当然还有更准确的说法:
实现多线程一共有两种方法。 他们分别是实现
Runnable
接口和继承Thread
类。 但是,我们看原理,其实Thread
类实现了Runnable
接口,并且看Thread
类的run()
方法,会发现其实那两种本质都是一样的。 不管是“继承Thread
类然后重写run()
”还是“实现Runnable
接口并传入Thread
类”在实现多线程的本质上,并没有区别,都是最终调用了start()
方法来新建线程。 当然还有其他的实现线程的方法,例如线程池等,它们也能新建线程,但是细看源码,从没有逃出过本质,也就是实现Runnable
接口和继承Thread
类。
四、结论
我们只能通过新建Thread
类这一种方式来创建线程,但是类里面的run()
方法有两种方式来实现,第一种是重写run()
方法,第二种实现Runnable
接口的run()
方法,然后再把该Runnable
实例传给Thread
类。除此之外,从表面上看线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不出刚才所说的范围。