FutureTask——另一种闭锁的实现

关于多线程,我们接触对多的,最基础,入门的可能就是实现Runnable接口继承Thead类,因为Java单继承的原因,通常建议是实现Runnable接口。但这种“简单”的线程会带来一个问题,写过的人都知道,不管是实现Runnable还是继承Thread类,他们都是实现public void run()方法,而这个方法可以看到是没有返回值的。但我们想执行一个线程得到它的返回值应该怎么做呢?此时FutureTask就要出场了。

首先我们还是需要定义一个线程,不同的是这个线程需要实现Callable接口。这是第一个不同的地方。

复制代码
 1 package futuretask;
 2 
 3 import java.util.concurrent.Callable;
 4 
 5 /**
 6  * Created by yulinfeng on 12/17/16.
 7  */
 8 public class Task implements Callable<String> {
 9     private String name;
10 
11     public Task(String name){
12         this.name = name;
13     }
14 
15     @Override
16     public String call() throws Exception {
17         String hello = hello(name);
18         return hello;
19     }
20 
21     public String hello(String name){
22         return "hello " + name;
23     }
24 }
复制代码

可以看到此时并不是实现run方法,而是一个叫call的方法,同时有返回值(返回值取决于所定义的泛型)。定义好这个线程过后,并不是直接将此线程的实例传入一个Thread类中执行start方法,这也是和没有返回值的线程不同的地方。

下面我们通过两种方法使用FutureTask,一个是通过线程池的方式创建线程,一个是直接创建一个线程。


通过多线程的方式创建线程:

复制代码
 1 package futuretask;
 2 
 3 import countdownlatch.TaskThread;
 4 
 5 import java.util.concurrent.*;
 6 
 7 /**
 8  * Created by yulinfeng on 12/17/16.
 9  */
10 public class Test {
11     public static void main(String[] args){
12         ExecutorService exec = Executors.newCachedThreadPool();     //创建一个线程池
13         Future<String> hello = exec.submit(new Task("kevin"));  //执行线程池的submit方法,执行callable线程,并返回一个Future对象
14         try {
15             System.out.println(hello.get());    //通过调用Future的get方法,获取线程的返回值
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         } catch (ExecutionException e) {
19             e.printStackTrace();
20         }
21     }
22 }
复制代码

这是第一种通过线程池创建线程的方式,通过调用线程的submit方法。如果是实现的Runnable方法,通过线程池创建线程执行的则是线程池的execute方法。一定注意调用线程返回值时使用的是Future的get方法来获取的返回值。


直接创建线程:

复制代码
 1 package futuretask;
 2 
 3 import countdownlatch.TaskThread;
 4 
 5 import java.util.concurrent.*;
 6 
 7 /**
 8  * Created by yulinfeng on 12/17/16.
 9  */
10 public class Test {
11     public static void main(String[] args){
12         FutureTask<String> future = new FutureTask<String>(new Task("kevin"));  //将线程传入FuturaTask的构造方法中
13         Thread thread = new Thread(future); 
14         thread.start();
15         try {
16             System.out.println(future.get());
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         } catch (ExecutionException e) {
20             e.printStackTrace();
21         }
22     }
23 }
复制代码

在调用get方法时,一定是在执行完线程后,不然将会一直阻塞。

其实仔细观察以上两张方法,第一种是Future接口,第二种是FutureTask类。FutureTask是Future接口的实现,为什么不能将Future接口传入Thread的构造方法中呢?原因很简单。在Thread的单个参数构造方法中,只能传入Runnable类型,而Future并没有继承Runnable接口,FutureTask则同时继承了Runnable接口和Future接口。

最后,那么FutureTask应该怎么来定义呢?其实和我们上一节将的CountDownLatch一样,它也是一种闭锁的实现。Future.get取决于任务的状态,如果任务已完成,那么get会立即返回结果,否则get将阻塞直到任务进入完成状态,然后返回结果或者抛出异常。FutureTask将计算结果从执行计算的线程传递到获取这个结果的线程,而FutureTask的规范确保了这种传递过程能实现结果的安全发布。——《Java并发编程实践》

 

posted @   OKevin  阅读(1202)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示