实现多线程的方法是1种还是2种还是4种?
网上的说法
。。。。。。。
正确的说法
实现多线程的官方正确方法:2种
(实现Runnable接口、继承Thread类)
实现Runnable接口代码示例:
package threadcoreknowledge.createthreads;
/*
* 方法一:实现Runnable接口
*/
public class RunnableStyle implements Runnable{
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
@Override
public void run() {
System.out.println("用Runnable方法实现线程");
}
}
继承Thread类代码示例:
package threadcoreknowledge.createthreads;
/*
* 方法二:继承Thread类
*/
public class ThreadStyle extends Thread{
@Override
public void run() {
System.out.println("用Thread方法实现线程");
}
public static void main(String[] args) {
new ThreadStyle().start();
}
}
实现Runnable接口好在哪里?
继承Thread类是不推荐的,因为它有以下的一些缺点:
1、从代码架构角度:具体的任务(run方法)应该和“创建和运行线程的机制(Thread类)”解耦,用runnable对象可以实现解耦。
2、使用继承Thread的方式的话,那么每次想新建一个任务,只能新建一个独立的线程,而这样做的损耗会比较大(比如重头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减小这样的损耗。
3、继承Thread类以后,由于Java语言不支持双继承,这样就无法再继承其他的类,限制了可扩展性。
通常我们优先选择方法1。
两种方法的本质对比
方法一和方法二,也就是“实现Runnable接口并传入Thread类”和“继承Thread类然后重写run()”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法的最主要区别在于run()方法的内容来源:
方法一:最终调用target.run();
方法二:run()整个都被重写
。
。
。
private Runnable target;
。
。
。
。
@Override
public void run() {
if (target != null) {
target.run();
}
}
同时使用Runnable、Thread两种方法会发生什么?
package threadcoreknowledge.createthreads;
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类重写run方法把原来的run方法都重写了,if判断这步已经没有了,因此,尽管Runnable接口被初始化了,也重写了run(),并传入给Thread类,但没有了target,实现Runnable接口的这个线程无法被执行。
@Override
public void run() {
//if判断这步已经没有了
if (target != null) {
target.run();
}
}
总结:
通常我们可以分为两类,Oracle也是这么说的
准确的讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式
方法一:实现Runnable接口的run方法,并把Runnable实例传给Thread类
方法二:重写Thread的run方法(继承Thread类)
定时器、线程池属于多线程的实现方式吗?
多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。
以上的观点之所以错误,是因为他们都只不过是包装了new Thread(),我们如果把能新建线程的类都称作是一种实现线程的方法,那么就太流于表面了,而没有理解到底层的原理。
而随着JDK的发展,这样的类会越来越多,我们肯定也没办法熟悉每一种具有新建线程能力的类,因为有些类根本不常用。
常见面试题:有多少种实现多线程的方式?
答题思路,有以下5点:
从不同的角度看,会有不同的答案。
典型答案是两种,分别是实现Runnable接口和继承Thread类,然后具体展开说;
但是,我们看原理,其实Thread类实现了Runnable接口,并且看Thread类的run方法,会发现其实那两种本质都是一样的,run方法的代码如下:
@Override
public void run() {
if (target != null) {
target.run();
}
}
方法一和方法二,也就是“继承Thread类然后重写run()”和“实现Runnable接口并传入Thread类”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法的最主要区别在于run()方法的内容来源:
方法一:最终调用target.run();
方法二:run()整个都被重写
然后具体展开说其他方式;
还有其他的实现线程的方法,例如线程池等,它们也能新建线程,但是细看源码,从没有逃出过本质,
也就是实现Runnable接口和继承Thread类。
结论:我们只能通过新建Thread类这一种方式来创建线程,但是类里面的run方法有两种方式来实现,第一种是重写run方法,第二种实现Runnable接口的run方法,然后再把该runnable实例传给Thread类。除此之外,从表面上看线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不出刚才所说的范围。
以上这种描述比直接回答一种、两种、多种都更准确。