🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈
文章目录
如标题所示,创建线程到底有几种方法?
答案是:不是固定的,可以说有两种,可以说有三种,可以说有四种,可以说有五种…
只要自圆其说即可,可不是瞎说哦 ~
同时,我们可以了解到,在计算机这个奇妙的世界里,很多问题答案并不是固定的,只要我们掌握里面的核心与精髓即可,尤其是像这种问题,根据学习者自己总结的类型,答案肯定是不固定的,每个人的理解不同,归纳的方式不同,所以,理解透彻,掌握核心,才是我们需要做的呀!
下面我们一起看看吧~
1. 创建线程的两种方式总结(最官方)
- 方式一:继承 Thread 类
- 方式二:实现 Runnable 接口
通过官方文档 oracle,可以看到,官方给的解释,创建线程的方式是两种,查看方式如下:
即为创建新的执行线程有两种方法,一种是将一个类声明为 Thread 的子类,该子类应重写 Thread 类的 run 方法,另一种方法是声明一个实现 Runnable 接口的类,该类实现 run 方法
1.1 继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
while(true) {
System.out.println("这是继承Thread创建线程");
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
}
}
1.2 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
while(true){
System.out.println("这是实现Runnable接口创建线程");
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable);
t.start();
}
}
其实本质上来说,创建线程的方式只有一种,就是通过 Thread 类实现,通过上述代码可以直观感受到,只是 Thread 类和 Runnable 接口这两种创建线程方式是通过 run() 方法的位置不同进行区分的。下面的几种创建线程方式的总结,归根到底还是上述的两种,究其本质发现,底层源码仍然是 Thread 类和 Runnable 接口实现的!
1.3 优先考虑使用第二种 —— 实现 Runnable 接口
理由有以下三个方面:
- 耦合度角度:实现Runnable接口创建线程, Thread线程与run方法耦合开;而继承Thread类创建线程,Thread线程与run方法耦合在一起,因此,优先考虑实现Runnable接口创建线程;
- 资源角度:每出现一个任务的时候,继承Thread类创建线程,都需要手动创建一个线程,线程的创建和销毁都会消耗比较大的资源,而实现Runnable接口的方式创建线程,仅需实现Runnable接口,将实现类作为参数加入到Thread()中即可,线程Thread通过线程池创建与管理,这样就可以减少线程的创建和销毁,因此,优先考虑实现Runnable接口创建线程;
- 继承角度:由继承的知识可以知道:Java支持单继承、多层继承、不同类继承同一类,但是不支持多继承即一个子类继承多个父类,因此,一个子类只能继承一个父类,继承Thread类创建线程,采用的是继承的方式,只能继承这一个类,可扩展性大大降低,并且,如果不同类继承同一个Thread类,可能出现父类的run方法被子类重写导致run方法被覆盖的情况,因此,优先考虑实现Runnable接口创建线程。
2. 创建线程的其它方式总结
在这期内容中,介绍了五种创建线程的方式,可回顾这期内容 3.1创建线程 ,Thread类及其基本用法
2.1 创建线程的五种方式总结一
可以看到这期内容介绍的五种创建线程方式如下:
- 方式一:使用继承Thread,重写run的方式
- 方式二:使用实现Runnable,重写run的方式
- 方式三:继承Thread,使用匿名内部类的方式
- 方式四:实现Runnable类,使用匿名内部类
- 方式五:lambda表达式(最推荐使用,最简单最直观写法)
显然易见,方式一和二与上面的创建线程的两种方式总结一致,而方式三,其实本质上还是使用继承Thread类,方式四、五本质上还是使用实现Runnable接口,只是使用的形式上是匿名类!
2.2 创建线程的五种方式总结二
- 方式一:继承Thread类
- 方式二:实现Runnable接口
- 方式三:基于lambda
- 方式四:实现Callable
- 方式五:线程池创建线程
为什么有人会把基于lambda表达式创建线程的方式进行总结,因为它是最常用的,最直观最简单的写法
这里对线程池创建线程和实现Callable创建线程进行介绍:
2.2.1 线程池创建线程
在日常学习或者是开发中,经常通过线程池来创建线程,那么线程池创建线程的本质到底是什么呢?
介绍线程池的文章可回顾这期内容:线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
线程池创建线程的方式,本质上来说,还是使用的是实现Runnable接口
2.2.2 实现 Callable 创建线程
与其它方式创建线程不同,实现 Callable 创建线程最大的优点是:可以返回执行完毕后的结果
1)创建任务对象
- 定义一个类,实现Callable接口,重写call方法,描述线程的任务以及返回后的数据
- 把Callable类型的对象封装成FutureTask,即线程任务
2)把线程任务对象交给Thread对象
3)使用Thread对象,调用start()方法启动线程
4)线程执行完毕后,通过FutureTask对象get方法获取线程任务执行结果
代码如下:
import java.util.concurrent.Callable;
//1.MyCallable类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n){
this.n = n;
}
//2.重写call方法
@Override
public String call() throws Exception {
//描述线程的任务以及返回后的数据,求1至n的和并返回结果
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return "线程求出的和为:" + sum;
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
//1.是一个实现Runnable的对象
//2.可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果
//3.创建一个Callable的对象
Callable<String> call = new MyCallable(10);
//4.把Callable的对象封装成一个FutureTask对象(任务对象)
FutureTask<String> f1 = new FutureTask<>(call);
//5.把任务对象交给一个Thread对象
Thread t1 = new Thread(f1);
t1.start();
//6.获取线程执行完毕后返回的结果
//如果代码执行到这,上面的线程t1还没有执行完,就会暂停,等待上面线程执行完毕之后才会获取结果
String ret = null;
try {
ret = f1.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(ret);
}
}
打印的结果为:
也可以使用匿名内部类的方式,直接重写call方法,代码如下:
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//只是创建了任务,还没有执行
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
//还需要找个人来完成这个任务即线程
//Thread 不能直接传callable 需要再包装一层
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
//此处的get就是获取到上述任务call方法返回值的结果
//调用get与join类似会阻塞等待,并获取到值
}
}
实现Callable创建线程的方式,本质上来说,还是使用的是实现Runnable接口
还有小伙伴总结,定时器工具类创建线程,下面进行简单介绍:
2.2.3 定时器工具类创建线程
还记得这个例子嘛!定时器同时定义 3 个任务,按照时间顺序依次执行,可回顾定时器这期内容:定时器
public class ThreadDemo30 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello1");
}
},1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello2");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello3");
}
},3000);
}
}
实现定时器工具类创建线程的方式,本质上来说,还是使用的是实现Runnable接口
💛💛💛本期内容回顾💛💛💛
✨✨✨本期内容到此结束啦~