多线程实现方式

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

但本质上来讲,个人认为只有一种方式:实现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

posted @   Vincent-yuan  阅读(70)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示