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