Java 多线程:获得线程的返回结果

本文介绍了在Java中如何获取线程执行后的返回结果,通过分析`Runnable`和`Callable`的区别,展示了使用`Runnable`结合`FutureTask`实现线程返回值的示例代码,解释了`Future`接口在获取线程结果中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们可以知道 Thread 自己也实现了 Runnable 接口,Threadrun方法的实现如下(Thread 启动之后运行的就是 Thread 中的 run 方法):

Thread 中 run 方法的实现

(target 即构造Thread 时可传入的 Runnable 对象,不传入即为 null —— 所以继承 Thread 重写其 run 方法也是一种创建线程的方式)

题外话结束 }

Runnable.java的代码:

Runnable.java 的代码

Runnable 的 run 方法是不带返回值的,那如果我们需要一个耗时任务在执行完之后给予返回值,应该怎么做呢?

import java.util.*;

public class RunnableTest {

public static void main(String[] args) throws Exception {

System.out.println(“使用 Runnable 获得返回结果:”);

List workers = new ArrayList<>(10);

List tasks = new ArrayList<>(10);

// 新建 10 个线程,每个线程分别负责累加 1~10, 11~20, …, 91~100

for (int i = 0; i < 10; i++) {

AccumRunnable task = new AccumRunnable(i * 10 + 1, (i + 1) * 10);

Thread worker = new Thread(task, “慢速累加器线程” + i);

tasks.add(task);

workers.add(worker);

worker.start();

}

int total = 0;

for (int i = 0, s = workers.size(); i < s; i++) {

workers.get(i).join();  // 等待线程执行完毕

total += tasks.get(i).getResult();

}

System.out.println("\n累加的结果: " + total);

}

static final class AccumRunnable implements Runnable {

private final int begin;

private final int end;

private int result;

public AccumRunnable(int begin, int end) {

this.begin = begin;

this.end = end;

}

@Override

public void run() {

result = 0;

try {

for (int i = begin; i <= end; i++) {

result += i;

Thread.sleep(100);

}

} catch (InterruptedException ex) {

ex.printStackTrace(System.err);

}

System.out.printf(“(%s) - 运行结束,结果为 %d\n”,

Thread.currentThread().getName(), result);

}

public int getResult() {

return result;

}

}

}

运行结果:

使用 Runnable 获得返回结果

第二种方法:使用 Callable<V>FutureTask<V>

Callable<V>JDK1.5 时添加的类,为的就是解决 Runnable 的痛点(没有返回值和不能抛出异常)。

Callable.java 的代码:

Callable.java 的代码

可以看到参数化类型 V就是返回的值的类型。

但是查看 Thread 的构造方法,我们发现 Thread 只提供了将 Runnable 作为参数的构造方法,并没有使用 Callable<V> 的构造方法 —— 所以引出 FutureTask<V>

FutureTask<V> 也是 JDK1.5 时添加的类,查看它的类声明:

FutureTask 的类声明

可以看到它实现了 RunnableFuture<V> 这个接口,我们再看看 RunnableFuture<V>

RunnableFuture 的接口声明

可以看到 RunnableFuture 接口继承了 Runnable 接口,那么 RunnableFuture 接口的实现类 FutureTask 必然会去实现 Runnable 接口 —— 所以 FutureTask 可以用来当 Runnable 使用。查看 FutureTask 的构造方法,发现 FutureTask 有两个构造方法:

FutureTask 的两个构造方法

第一个构造方法表明我们可以通过一个 Callable 去构造一个 FutureTask —— 而 FutureTask 实现了 Runnable 接口,从而可以将该任务传递给 Thread 去运行;

第二个构造方法是通过一个 Runnable 和一个指定的 result 去构造 FutureTask


我们再来看看 FutureTask<V> 通过 RunnableFuture<V> 实现的第二个接口:Future<V>

(事实上,RunnableFuture<V> 是在 JDK1.6 时添加的类,我猜测在 JDK1.5FutureTask<V> 应该是直接实现的 RunnableFuture<V>,而不是通过 RunnableFuture<V>)

Future.java 的源码:

Future.java 的源码

Future<V> 包含的方法有 5 个,本文只关注它两个 get 相关的方法。通过 Java 的 API 文档,我们可以知道 get 方法是用来返回和 Future关联的任务的结果(即构造 FutureTask<V> 时使用的 Callable<V> 的结果)。带参数的 get 方法指定一个超时时间,在超时时间内该方法会阻塞当前线程,直到获得结果之后停止阻塞继续运行 —— 如果在给定的超时时间内没有获得结果,那么便抛出 TimeoutException 异常;不带参数的 get 可以理解为超时时间无限大,即一直等待直到获得结果。

(Future 每个方法的详细用法可以参考 Java 多线程(3))

通过以上我们可以知道,Callable<V>Future<V>FutureTask<V> 这三个类是相辅相成的。以上提到关键类的类关系如下:

以上几个关键类的关系

现在我们看看 FutureTask 中实现 Runnablerun 方法是怎样的(我们直接看关键代码):

FutureTask 实现的 run 方法的关键代码

代码的意思很明确,调用构造 FutureTask<V> 时传入的 Callable<V>call 方法,如果正常执行完毕,那么通过 set(result) 设置结果,通过 get()方法得到的即为这个结果 —— 思路和前面第一种方法是一致的(当然 FutureTask是更强大和更通用的类);否则即为抛出异常的情况。

使用 FutureTask<V> 的过程如下:

(1)通过一个 Callable<V> 任务或者一个 Runnable(一开始就指定 result)任务构造 FutureTask<V>

(2)将 FutureTask<V> 交给 Thread 去运行;

(3)使用 FutureTask<V>get 方法(或者 Threadjoin 方法)阻塞当前线程直到获得任务的结果。


现在我们使用 Callable 改写程序:

import java.util.*;

import java.util.concurrent.*;

public class CallableTest {

public static void main(String[] args) throws Exception {

System.out.println(“使用 Callable 获得返回结果:”);

List<FutureTask> futureTasks = new ArrayList<>(10);

// 新建 10 个线程,每个线程分别负责累加 1~10, 11~20, …, 91~100

for (int i = 0; i < 10; i++) {

AccumCallable task = new AccumCallable(i * 10 + 1, (i + 1) * 10);

FutureTask futureTask = new FutureTask<>(task);

futureTasks.add(futureTask);

Thread worker = new Thread(futureTask, “慢速累加器线程” + i);

worker.start();

}

int total = 0;

for (FutureTask futureTask : futureTasks) {

total += futureTask.get(); // get() 方法会阻塞直到获得结果

}

System.out.println("累加的结果: " + total);

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。

如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。


《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
“img” style=“zoom: 33%;” />

总结

总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。

如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。

[外链图片转存中…(img-iuy6we0A-1713393850322)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值