尝试在某度上搜索可以发现实现线程的方式有很多种不同的解读,又说2种的又说3种的又说四种的等等…
(1)我们其实可以看看oracle官方是如何阐述的可以看到官方对创建线程的描述是两种 1.一种是创建Thread的子类对象 2.一种是实现runnable接口来创建
(2)实际代码演示一下吧继承Thread方式实现线程的创建
public class ThreadStyleOne extends Thread{ @Override public void run() { System.out.println("使用Thread方式创建多线程"); } public static void main(String[] args) { new ThreadStyleOne().start(); } }通过实现Runnbale接口的方式实现线程的创建
public class ThreadStyleTwo implements Runnable{ @Override public void run() { System.out.println("通过继承Runnable接口方式实现多线程"); } public static void main(String[] args) { Thread thread = new Thread(new ThreadStyleTwo()); thread.start(); } }那么这两种方式那种方式更好呢? 答案是实现runnable方式的更好一点,可以从三个维度分析
从解耦的角度考虑继承Thread方式的实现,其对象的创建和执行是一起操作的从资源损耗的角度来看,每执行一个新的线程Thread的方式就包括线程的创建执行和销毁,这其实是我们相当不提倡的一种方式从java语法的角度考虑,java是不支持多继承的,所以使用Thread的方式就会造成线程的可扩展性很差,而使用runnable接口的方式就不存在这个问题 (3)两者的区别首先我们进入到Thread对象的源码中观察一下,可以看到如下的代码
@Override public void run() { //如果target不是null的就执行target类里的run()方法 if (target != null) { target.run(); } } //那么target是什么呢?往上找可以看到这样的定义 /* What will be run. */ private Runnable target;所以分析可以得Thread对象在执行线程程序的时候会先判断,是否有runnable对象传入,如果有就执行runnable对象里的run方法
所以通过上面的分析我们可以思考一下下面的问题
同时使用thread方法和runnable来创建想成,会是一个怎么的结果
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
解释:可能有些人会认为是我是来自runnable 其实果然是同时使用两种方式,Thread的run方法是被其匿名内部类中的run方法直接覆盖掉了,也就不存在target!=null的那段判断逻辑,所以接口是我来自Thread,你答对了吗?
(4)总结:创建线程的准确描述通常可以将创建线程分为两种,主要是Oracle官方也是这么介绍的 但是准确的将讲创建线程只有一种方式,就是构造Thread类,而实现线程的执行单元有两种方式
方法1:实现Runnable接口的run()方法,并把Runnable对象实例传递给Thread对象
方法2:继承Thread对象重写里面的run()方法
(5)典型的错误观点
1.线程池也是一种创建新建线程的方式 2.通过Callable和FutureTask创建线程,也算是一种新建线程的方式 3.无返回值实现Runnable接口,有返回值实现Callable接口,所有Callable是实现新的线程方式 4.定时器创建线程 5.匿名内部类是创建新线程的一种方式 6.Lambda表达式是创建线程的一种方式
线程池方式 public class ThreadPoolStyle { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 1000; i++) { executorService.submit(new Task() { }); } } } //本质上还是继承Runable接口实现新线程的创建 class Task implements Runnable { @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } 通过Callable和FutureTask创建线程通过观察callable和futuretask查看接口关系图可以看出来他们的并没有脱离runnbale来创建线程的范畴
定时器创建多线程 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); } //进入到TimeTask可以看到,实际上它还是实现Runnable接口 public abstract class TimerTask implements Runnable { Lambda表达式创建线程/匿名内部类方式创建线程 public class Lambda { public static void main(String[] args) { new Thread(() -> System.out.println(Thread.currentThread().getName())).start(); } } new Thread(){ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }.start();
其实那么多种方式总结起来就是:他们都是直接或间接的使用Thread方式和runnable方式线程线程的创建本质上时没有区别的