原:https://www.jianshu.com/p/7950ea349dbb
多线程有几种实现方式?如果被问到这个问题一定很头疼,因为百度一下随便就能出现各种各样的答案。两种、三种、四种、五种、六种、七种。。。
但本质上来讲,个人认为只有一种方式:实现Runnable接口。
先放个图:
1、实现Runnable接口
public class DemoThreadTask implements Runnable{ @Override public void run() { // TODO Auto-generated method stub } public static void main(String[] args) { DemoThreadTask task = new DemoThreadTask(); Thread t = new Thread(task); t.start(); ... } }
实现Runnable接口,利用Runnable实例构造Thread,是较常用且最本质实现。此构造方法相当于对Runnable实例进行一层包装,在线程t
启动时,调用Thread的run方法从而间接调用target.run():
public class Thread implements Runnable { /* What will be run. */ private Runnable target; public void run() { if (target != null) { target.run(); } } ... }
2、继承Thread类
public class DemoThread extends Thread{ @Override //重写run方法 public void run() { // TODO Auto-generated method stub } public static void main(String[] args) { DemoThread t = new DemoThread(); t.start(); ... } }
这种实现方式是显示的继承了Thread,但从类图中我们可以看到,Thread类本身就继承自Runnable,所以继承Thread的本质依然是实现Runnable接口定义的run方法。
需要注意的是继承Thread方式,target对象为null,重写了run方法,导致方式1中的Thread原生的run方法失效,因此并不会调用到target.run()的逻辑,而是直接调用子类重写的run方法。
因为java是单根继承,此方式一般不常用。
3、实现Callable接口并通过FutureTask包装
先看demo:
public class DemoCallable implements Callable<String>{ @Override public String call() throws Exception { // TODO Auto-generated method stub return null; } public static void main(String[] args) throws Exception { DemoCallable c = new DemoCallable(); FutureTask<String> future = new FutureTask<>(c); Thread t = new Thread(future); t.start(); ... String result = future.get(); //同步获取返回结果 System.out.println(result); } }
实现Callable接口通过FutureTask包装,可以获取到线程的处理结果,future.get()方法获取返回值,如果线程还没执行完,则会阻塞。
这个方法里,明明没有看到run方法,没有看到Runnable,为什么说本质也是实现Runnable接口呢?
回看开篇的类图,FutureTask实现了RunnableFuture,RunnableFuture则实现了Runnable和Future两个接口。因此构造Thread时,FutureTask还是被转型为Runnable使用。因此其本质还是实现Runnable接口。
至于FutureTask的工作原理,后续篇章继续分析。
4、匿名内部类
匿名内部类也有多种变体,上述三种方式都可以使用匿名内部类来隐式实例化。
public class Demo{
public static void main(String[] args) throws Exception { //方式一:Thread匿名内部类 new Thread(){ @Override public void run() { // TODO Auto-generated method stub } }.start(); //方式二:Runnable匿名内部类 new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); ... } }
匿名内部类的优点在于使用方便,不用额外定义类,缺点就是代码可读性差。
5、Lambda表达式
Lambda表达式是jdk8引入的,已不是什么新东西,现在都jdk10了。demo如下:
public class Demo{
public static void main(String[] args) throws Exception { new Thread(() -> System.out.println("running") ).start() ; ... } }
如此简洁的Lambda表达式,有没有吸引到你呢?当然本质不多说,还是基于Runnable接口。
6、线程池
public class DemoThreadTask implements Runnable{ @Override public void run() { // TODO Auto-generated method stub System.out.println("running"); } public static void main(String[] args) { DemoThreadTask task = new DemoThreadTask(); ExecutorService ex = Executors.newCachedThreadPool(); ex.execute(task); ... } }
线程池与前面所述其他方式的区别在于执行线程的时候由ExecutorService去执行,最终还是利用Thread创建线程。线程池的优势在于线程的复用,从而提高效率。
关于线程池,后续篇章会继续详解。
7、定时器
public class DemoTimmerTask {
public static void main(String[] args) throws Exception { Timer timer = new Timer(); timer.scheduleAtFixedRate((new TimerTask() { @Override public void run() { System.out.println("定时任务1执行了...."); } }), 2000, 1000); } }
TimerTask的实现了Runnable接口,Timer内部有个TimerThread继承自Thread,因此绕回来还是Thread + Runnable。
总结,多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。
作者:徐志毅
链接:https://www.jianshu.com/p/7950ea349dbb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。