# 面试题:
- Java中创建线程有几种方式。
- 不同的创建方式有什么区别。
- 如何启动一个线程。
# Java中创建线程的方式
- JDK源码中的描述:两种
- 第一种是继承Thread类,重写其run()方法()。
- 第二种是实现Runnable接口,重写run()方法,再将Runnable实例传给Thread,Thread类最终会调用
target.run()
(target即为Runnable实例)方法来执行。**
- 代码演示
/** * 通过继承Thread类,重写run()方法实现的线程 * * @author futao * @date 2020/6/4 */ public class ByThread extends Thread { @Override public void run() { System.out.println("通过继承Thread实现的多线程:" + Thread.currentThread().getName()); } public static void main(String[] args) { new ByThread().start(); } } /** * 通过实现Runnable接口,重写run()方法实现的线程 * * @author futao * @date 2020/6/4 */ public class ByRunnable implements Runnable { @Override public void run() { System.out.println("通过实现接口Runnable实现的多线程:" + Thread.currentThread().getName()); } public static void main(String[] args) { new Thread(new ByRunnable()).start(); } }
# 核心思想
从源代码的角度来看,创建线程的方式只有一种,唯一的途径就是实例化一个Thread对象,通过thread.start()
来启动线程。而继承Thread类和实现Runnable接口,只不过是对方法执行单元(即run()
方法)的两种不同实现。**
# 两种方式的对比:
- 从代码架构角度:使用Runnable接口得我方式可以将线程的创建/停止/状态管理等与真正的业务逻辑解耦,使Runnable子类只需要关注真正的业务即可。
- 从性能损耗角度:使用继承Thread的方法,每次执行任务都需要启动一个新的线程,创建一个新的Thread实例,任务执行完毕之后还需要进行销毁。对性能的损耗比较严重。而实现Runnable接口的方式,可以实现对线程的复用,每次给线程传递不同的任务(Runnable实例)即可,不需要频繁的创建与销毁线程。(参考线程池的执行过程)
- 从可扩展性角度:Java只支持单继承,所以实现Runnable接口的方式更好,避免继承的局限,方便后续对程序进行扩展。
# Q: 如果同时继承Thread类和实现Runnable接口,会发生什么?
/** * @author futao * @date 2020/6/4 */ public class Both { public static void main(String[] args) { new Thread(() -> { //通过lambda表达式创建Runnable子类对象 System.out.println("来自实现Runnable接口的run()方法"); }) { //Thread的匿名内部类,直接重写Thread父类的run()方法 @Override public void run() { System.out.println("来自重写Thread类的run()方法"); } }.start(); } }
- 从结果可以看到,最终线程执行的是匿名内部类的run()方法。原因是在我们将Thread的start()方法重写之后不会再执行调用Runnable.run()方法。而执行我们重写之后的run()方法。
@Override public void run() { if (target != null) { target.run(); } }
# 错误观点
- 线程池: 通过线程工厂
ThreadFactory.newThread()
创建线程,而ThreadFactory.newThread()
方法中也是通过实例化Thread对象的方式创建线程。
ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r); } });
- 通过
FutureTask<>
与Callable
: FutureTask实际上实现了Runnable接口,并且在重写的run()方法中调用了传递进来的Callable对象的call()方法。所以这种方式只不过是对Runnable方式的一种封装而已。本质上也只是实现线程执行单元的一种方法,最终需要将FutureTask对象传入Thread()对象进行执行。
/** * @author futao * @date 2020/6/6. */ public class CallableFutureTask { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<String> task = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { System.out.println("666"); Thread.sleep(3000L); return Thread.currentThread().getName(); } }); new Thread(task).start(); //将会阻塞,直到线程任务执行完毕 System.out.println(task.get()); } }
# 线程的启动
- 调用Thread对象的
start()
方法。三个过程:
- 判断线程状态。
- 加入线程组。
- 执行native方法
start0()
。
- 直接调用
run()
方法:只是普通方法调用,不会开启新的线程。 start()
方法只能被调用一次,如果第二次调用,将抛出异常,即启动过程的第一步:检查线程状态不通过。