网上和书籍的各种说法:鱼龙混杂
正确的说法
方法一:实现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接口更好一些原因如下:
run方法源码解析:
/* What will be run. */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
实现Runnable接口最终会调用 target.run() 但是继承Thread类的run方法整个都被重写。
继承Thread有如下缺点:
1、从代码的架构考虑的话:run方法里面的内容(对应我们具体执行的任务),它应该和我们和线程的创建、运行等机制也就是Thread类是解耦的,所以不应该把这两个事情混为一谈,从解耦的角度是有缺点的。
2、如果说继承了Thread类,用这种方法的话,每次如果我们需要新建一个任务,只能去新建一个独立的线程,而新建一个独立的线程这样的损耗是比较大的,需要创建、执行、销毁等,而如果使用Runnable接口的话,后面就会使用线程池等一些工具,就可以大大减小创建线程带来的损耗,从资源节约的角度上也是优缺点的。
3、Java不支持多继承,如果继承了Thread类就不可以再去继承其他的类,大大限制了可扩展性。
同时使用这两种方法会怎么样:
/**
* 描述: 同时使用Runnable和Thread两种实现线程的方式
*/
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();
}
}
运行之后:
分析:这是因为run方法被重写了,这是target.run就不会执行了。
常见错误观点:
1、“线程池创建线程也是一种新建线程的方式”
虽然利用线程成可以创建线程,但是这只是表象,不属于创建线程的本质。通过深入Executors
的源码可以看到,在线程池的内部,也是新建了Thread类来执行这些任务的。
2、“通过Callable和FutureTAsk创建线程,也算是一种新建线程的方式。”
3、“无返回值是实现runnable接口,有返回值是实现callable接口,所以callable是新的实现线程的方式”
4、“匿名内部类”
5、“Lambda表达式”
综上所述,这些错误观点的本质都是离不开Runnable接口和Thread类的。都属于表明现象。多线程的实现方式,在代码中写法千变万化,但本质不变。