多线程实现方式

多线程有几种实现方式?如果被问到这个问题一定很头疼,因为百度一下随便就能出现各种各样的答案。两种、三种、四种、五种、六种、七种。。。

但本质上来讲,个人认为只有一种方式:实现Runnable接口。

先放个图:

多线程实现方式

 

 

 1、实现Runnable接口

 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类

 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包装

 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、匿名内部类

匿名内部类也有多种变体,上述三种方式都可以使用匿名内部类来隐式实例化。

 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如下:

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、线程池

 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、定时器

 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

上一篇:多线程基础


下一篇:线程安全问题