我们可以知道 Thread
自己也实现了 Runnable
接口,Thread
中 run
方法的实现如下(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.5
时 FutureTask<V>
应该是直接实现的 Runnable
和 Future<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
中实现 Runnable
的 run
方法是怎样的(我们直接看关键代码):
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
方法(或者 Thread
的 join
方法)阻塞当前线程直到获得任务的结果。
现在我们使用 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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
存中…(img-CiVTymUt-1715344940445)]
[外链图片转存中…(img-iIKtkETa-1715344940446)]
[外链图片转存中…(img-BqJ4YE7f-1715344940446)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!