当前位置: 首页 > news >正文

深入浅出获取线程的不同方式

在上一篇博客中,我们提到了创建线程的 3 种方式,一种是直接继承 Thread,一种是实现 Runnable 接口,另外一种是实现 Callable 接口。
前 2 种方式都有一个缺陷:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者线程通信的方式来达到目的,这样使用起来就比较麻烦。
Java 1.5 提供了 Callable、Future、FutureTask,它们可以在任务执行完后得到执行结果,今天我们就来详细的了解一下。

无返回值的 Runnable

由于Runnablerun()方法的返回值为 void:
public interface Runnable { public abstract void run(); }
所以在执行完任务之后无法返回任何结果。

有返回值的 Callable

Callable 位于java.util.concurrent包下,也是一个接口,它定义了一个call()方法:
public interface Callable<V> { V call() throws Exception; }
可以看到,call()方法返回的类型是一个 V 类型的泛型。
那怎么使用 Callable 呢?
一般会配合 ExecutorService来使用。
ExecutorService 是一个接口,位于java.util.concurrent包下,它是 Java 线程池框架的核心接口,用来异步执行任务。它提供了一些关键方法用来进行线程管理。
下面的例子就用到了 ExecutorService 的 submit 方法。
// 创建一个包含5个线程的线程池 ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个Callable任务 Callable<String> task = new Callable<String>() { public String call() { return "Hello from " + Thread.currentThread().getName(); } }; // 提交任务到ExecutorService执行,并获取Future对象 Future[] futures = new Future[10]; for (int i = 0; i < 10; i++) { futures[i] = executorService.submit(task); } // 通过Future对象获取任务的结果 for (int i = 0; i < 10; i++) { System.out.println(futures[i].get()); } // 关闭ExecutorService,不再接受新的任务,等待所有已提交的任务完成 executorService.shutdown();
我们通过 Executors 工具类来创建一个 ExecutorService,然后向里面提交 Callable 任务,然后通过 Future 来获取执行结果。
为了做对比,我们再来看一下使用 Runnable 的方式:
// 创建一个包含5个线程的线程池 ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个Runnable任务 Runnable task = new Runnable() { public void run() { System.out.println("Hello from " + Thread.currentThread().getName()); } }; // 提交任务到ExecutorService执行 for (int i = 0; i < 10; i++) { executorService.submit(task); } // 关闭ExecutorService,不再接受新的任务,等待所有已提交的任务完成 executorService.shutdown();
可以看到,使用 Runnable 的方式要比 Callable 的方式简单一些,但是 Callable 的方式可以获取执行结果,这是 Runnable 做不到的。

异步计算结果 Future 接口

在前面的例子中,我们通过 Future 来获取 Callable 任务的执行结果,那么 Future 是什么呢?
Future 位于java.util.concurrent包下,它是一个接口:
public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
一共声明了 5 个方法:
  • cancel()方法用来取消任务,如果取消任务成功则返回 true,如果取消任务失败则返回 false。参数 mayInterruptIfRunning 表示是否允许取消正在执行却没有执行完毕的任务,如果设置 true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论 mayInterruptIfRunning 为 true 还是 false,此方法肯定返回 false,即如果取消已经完成的任务会返回 false;如果任务正在执行,若 mayInterruptIfRunning 设置为 true,则返回 true,若 mayInterruptIfRunning 设置为 false,则返回 false;如果任务还没有执行,则无论 mayInterruptIfRunning 为 true 还是 false,肯定返回 true。
  • isCancelled()方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  • isDone()方法表示任务是否已经完成,若任务完成,则返回 true;
  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回 null。
也就是说 Future 提供了三种功能:
  • 1)判断任务是否完成;
  • 2)能够中断任务;
  • 3)能够获取任务执行结果。
由于 Future 只是一个接口,如果直接 new 的话,编译器是会有一个警告的,它会提醒我们最好使用 FutureTask。
实际上,FutureTask 是 Future 接口的一个唯一实现类,我们在前面的例子中executorService.submit()返回的就是 FutureTask。

异步计算结果 FutureTask 实现类

我们来看一下 FutureTask 的实现:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask 类实现了 RunnableFuture 接口,我们看一下 RunnableFuture 接口的实现:
public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }
可以看出 RunnableFuture 继承了 Runnable 接口和 Future 接口,而 FutureTask 实现了 RunnableFuture 接口。所以它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。
FutureTask 提供了 2 个构造器:
public FutureTask(Callable<V> callable) { } public FutureTask(Runnable runnable, V result) { }
当需要异步执行一个计算并在稍后的某个时间点获取其结果时,就可以使用 FutureTask。举个🌰
// 创建一个固定大小的线程池 ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建一系列 Callable Callable<Integer>[] tasks = new Callable[5]; for (int i = 0; i < tasks.length; i++) { final int index = i; tasks[i] = new Callable<Integer>() { @Override public Integer call() throws Exception { TimeUnit.SECONDS.sleep(index + 1); return (index + 1) * 100; } }; } // 将 Callable 包装为 FutureTask,并提交到线程池 FutureTask<Integer>[] futureTasks = new FutureTask[tasks.length]; for (int i = 0; i < tasks.length; i++) { futureTasks[i] = new FutureTask<>(tasks[i]); executorService.submit(futureTasks[i]); } // 获取任务结果 for (int i = 0; i < futureTasks.length; i++) { System.out.println("Result of task" + (i + 1) + ": " + futureTasks[i].get()); } // 关闭线程池 executorService.shutdown();
以下是程序的执行结果:
http://www.jsqmd.com/news/248700/

相关文章:

  • 亲测好用!9款AI论文工具测评:本科生毕业论文必备指南
  • 所谓的温湿度监控设备具备远程监控功能如何让管理者轻松获得数据?
  • 基于企业数据构建可扩展AI:Nemotron RAG与SQL Server 2025整合架构解析
  • 深入浅出线程组和线程优先级
  • Java主流连接池详解:特性、优缺点与适用场景
  • 学霸同款2026继续教育一键生成论文工具TOP9测评
  • Java版LeetCode热题100之搜索二维矩阵 II:从暴力到最优解的完整解析
  • C/C++ 将char[] 打印成二进制
  • 2小时,我搭了一套供应链全流程可视化系统,采购、计划、仓库终于对齐了
  • Java版LeetCode热题100之反转链表:从迭代到递归的全面解析
  • 大模型知识库(1)什么是Claude Skills?
  • Java版LeetCode热题100之回文链表:从数组复制到快慢指针的深度解析
  • Java版LeetCode热题100之相交链表:从哈希到双指针的深度解析
  • Java版LeetCode热题100之环形链表:从哈希表到Floyd判圈算法的深度解析
  • 码字1年,我测废了10个AI写小说软件,最后只推荐这5款小说软件生成器!
  • 富文本控件怎样提升XHEDITOR对Word公式粘贴的兼容性?
  • 不得了!实力天玑AIGEO优化系统代理大揭秘
  • 医院HIS系统怎样实现检验报告公式转XHEDITOR在线编辑?
  • Java版LeetCode热题100之环形链表 II:从哈希表到Floyd判圈算法的深度解析
  • 国防项目如何实现加密Word文档公式安全导入XHEDITOR?
  • 军工领域,JAVA大文件分块上传的示例代码是什么?
  • 站群系统如何处理PDF公式转存为XHEDITOR网页格式?
  • 医疗领域,JAVA大文件上传与下载的示例步骤?
  • 汽车制造行业,JAVA如何实现设计图纸的大文件上传示例?
  • 【文献分享】LyMOI一种结合深度学习和大规模语言模型的用于解读组学数据的工作流程
  • 5.1 办公自动化革命:让AI处理90%的重复性文档工作
  • 5.1 办公自动化革命:让AI处理90%的重复性文档工作
  • 别再手动写代码了!Claude Skills 实战,让 AI 帮你干 80% 的活!
  • 5.3 PPT制作效率爆炸提升:Gamma助力非设计专业也能做出精美演示文稿
  • 5.3 PPT制作效率爆炸提升:Gamma助力非设计专业也能做出精美演示文稿