1、实现多线程的方法是1种,2种还是4种?
Oracle官网的文档是如何写的?
- 方法一:实现Runnable接口
- 方法二:继承Thread类
1.1 实现示例
- 实现Runnable接口
/**
* RunnableStyle
*
* @author venlenter
* @Description: 用Runnable方式创建线程
* @since unknown, 2020-03-23
*/
public class RunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("用Runnable方法实现线程");
}
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
}
//输出结果
用Runnable方法实现线程
- 继承Thread类
/**
* ThreadStyle
*
* @author venlenter
* @Description: 用Thread方法实现线程
* @since unknown, 2020-03-23
*/
public class ThreadStyle extends Thread {
@Override
public void run() {
System.out.println("用Thread方法实现线程");
}
public static void main(String[] args) {
Thread thread = new ThreadStyle();
thread.start();
}
}
//输出结果
用Thread方法实现线程
1.2 两种方法的对比
方法1(实现Runnable接口)更好
两种方法的本质对比
- (Runnable):最终调用target.run();
实际上执行的是target的run方法
Thread.java
——————————————————————————————————————————
private Runnable target; //target是我们传入的new RunnableStyle()
@Override
public void run() {
if (target != null) {
target.run();
}
}
——————————————————————————————————————————
- (Thread):run()整个都被重写
调用的是ThreadStyle override的run方法
2、同时使用两种方法:正确实现方法的总结
- 实现了Runnable的run方法,但最终被Thread override的run覆盖,所以只打印了Thread的
/**
* BothRunnableThread
*
* @author venlenter
* @Description: 同时使用Runnable和Thread
* @since unknown, 2020-03-23
*/
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();
}
}
//输出结果
我来自Thread
2.1 总结
通常我们可以分为2类,Oracle官方文档也是这样描述
准确的说,创建线程只有1种方式,那就是构造Thread类,而实现线程的执行单元有2种方式
- 方法一:实现Runnable接口,重写run方法,并把Runnable实例传给Thread类
- 方法二:重写Thread的run方法(继承Thread类)
3、典型错误观点
- 线程池创建线程也算是一种新建线程的方式(本质也是通过Thread的方式)
/**
* ThreadPool5
*
* @author venlenter
* @Description: 线程池创建线程的方法
* @since unknown, 2020-03-24
*/
public class ThreadPool5 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.submit(new Task(){});
}
}
}
class Task implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
//输出结果
pool-1-thread-1
pool-1-thread-2
...
pool-1-thread-999
pool-1-thread-1000
- 通过Callable和FutureTask创建线程,也算是一种新建线程的方式(本质实现了Runnable接口)
- “无返回值”是实现Runnable接口,“有返回值”是实现callable接口,所以callable是新的实现线程的方式(同上,本质都是实现了Runnable)
- 定时器(TimerTask implements Runnable)
/**
* DemoTimmerTask
*
* @author venlenter
* @Description: 定时器创建线程,定时1s打印当前线程名
* @since unknown, 2020-03-29
*/
public class DemoTimmerTask {
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 1000, 1000);
}
}
//输出结果
Timer-0
Timer-0
Timer-0
- 匿名内部类
/**
* AnonymouslnnerClassDemo
*
* @author venlenter
* @Description: 匿名内部类创建线程
* @since unknown, 2020-03-29
*/
public class AnonymouslnnerClassDemo {
public static void main(String[] args) {
//方式1,直接new Thread重写run方法
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();
//方式2,传入一个Runnable()
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
//输出结果
Thread-0
Thread-1
- Lambda表达式
/**
* Lambda
*
* @author venlenter
* @Description: 用lambda方式创建线程
* @since unknown, 2020-03-29
*/
public class Lambda {
public static void main(String[] args) {
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
}
}
3.1 总结:多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。就是通过2种方式:继承Thread类;实现Runnable接口
4、实现多线程常见面试问题
4.1、有多少种实现线程的方法?思路有5点
- 从不同的角度看,会有不同的答案
- 典型答案是2种(继承Thread类、实现Runnable接口)
- 实际上,实现Runnable接口更好一点(有3点优点-见下方4.2)。
- Runnable方式,最终调用的是传入的Runnable对象的run()方法
public class RunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("用Runnable方法实现线程");
}
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
//target.run(),target即new RunnableStyle
thread.start();
}
}
- 而继承Thread类的方式,是重写了当前Thread类的run()方法
- 但实际上,看原理,两种本质都是一样的
Thread.java
private Runnable target; //target是我们传入的new RunnableStyle()
@Override
public void run() {
if (target != null) {
target.run();
}
}
实际上都是利用了Thread类的run方法。只不过一个是重写了Thread类的run方法,一个是传进来target对象,再去执行target的run方法
- 其实还有其他创建方式,如线程池、定时器等方式,但本质上还是使用了上面的2种方式
- 总的来说:本质只有一种,新建线程必须通过Thread类,但通常我们把它区分为2种形式(一种是继承Thread类,一种是实现Runnable接口),另外还有更多的表现形式(如线程池、定时器、匿名内部类、Lambda)
4.2、实现Runnable接口和继承Thread类哪种方式更好
- 从代码架构角度
- 这里有2件事情,第一是具体的功能,即run方法;
- 第二是跟线程生命周期相关的(创建线程、运行线程),这个实际上是Thread类才去做的事情,从代码设计上来说,应该解耦出来,所以用Runnable接口的方式更好
- 新建线程的损耗
- 如果用Thread的方式,每次需要new一个Thread对象,新建一个线程,执行完还需要销毁
- 如果用Runnable的方式,传入实现Runnable的对象,就可以反复使用这个线程,线程池就是这样做的,这样用于生命周期的损耗就减少了
- Java不支持双继承
笔记来源:慕课网悟空老师视频《Java并发核心知识体系精讲》