一、启动线程的两种方式
①实现Runnable接口
②继承Thread类
二、代码实现
实现Runnable接口:
public class CreatThreads1 implements Runnable{
public static void main(String[] args){
Thread thread = new Thread(new CreatThreads1());
thread.start();
}
@Override
public void run() {
System.out.println("实现Runnable接口-启动线程");
}
}
继承Thread类:
public class CreatThread2 extends Thread{
public static void main(String[] args) {
new CreatThread2().start();
}
@Override
public void run() {
System.out.println("继承Thread类-启动线程!");
}
}
三、哪个更好呢?
我们一般都先考虑实现Runnable接口这种方式,
继承Thread类的缺点:
①从代码的架构角度:具体执行的这个任务也就是run方法应该和我们线程的创建机制,也就是Thread类,它应该是解耦的。从解耦的角度。
.②从资源节约的角度:每次我们新建一个任务,我都得新建一个独立的线程,而新建一个独立的线程的损耗是比较大的。如果我们使用Runnable,就可以使用后续的线程池工具,而这些工具大大减小创建线程和销毁线程带来的损耗。
③从继承的角度:java不支持多继承,继承了Thread类,就不能继承别的类了,大大的削减了程序的扩展性。
两种方法的本质:
实现Runnable接口:最终调用target.run();
说明:在实现Runnable接口里的run()后,再创建一个Runnable实例传入Thread类,这个Runnable实例也是就任务(target),然后Thread类中的run()方法判断任务是否为空,不为空则执行接口里的run().
继承Thread类:run()方法整个被重写
说明:Thread类中的run()方法被从写,则不会执行Thread类中的run(),直接会执行重写的。
源码:
public void run() {
if (this.target != null) {
this.target.run();
}
}
所以开启一个线程的本质就是Thread类的那个run(),看是否传入target,还是重写run();
四、面试
有多少种实现多线程的方式?
思路:
1、从不同的角度看,会有不同的答案。
2、典型答案有两种,分别是实现Runnable接口和继承Thread类,然后展开说;
3、但是,我们看原理,其实Thread类实现了Runnable接口,并且看Thread类的run方法,会发现其实那两种本质都是一样的,run方法的代码如下:
@verride
public void run(){
if (target != null){
target.run();
}
}
方法一和方法二,也就是“继承Thread类然后重写run()”和实现Runnable接口并传入Thread类”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法最主要区别在于run()方法的内容来源:
方法一:最终调用target.run();
方法二:run()整个都被重写
4、然后具体展开说其他方式:
还有其他方式,例如线程池等,他们也能新建线程,但是细看源码,从没有逃过本质,也就是实现Runnable接口和继承Thread类。
5、结论:我们只能通过新建Thread类这一种方式来创建线程,但是类里面的run方法有两种方式来实现,第一种就是重写run方法,第二种实现Runnable接口的run方法,然后再把该runnable实例传给Thread类。除此之外,从表面上看线程池、定时器等工具也可以创建线程,但是他们的本质都逃不出刚才所说的范围。