Java 的多线程机制本质上能够完成两件事情,异步计算和并发,FutureTask 是基于 Runnable 实现的一个可取消的异步调用 API,本文给大家介绍Java 多线程并发FutureTask及基本使用,需要的朋友可以参考下
本文基于最新的 OpenJDK 代码,预计发行版本为 19 。
Java 的多线程机制本质上能够完成两件事情,异步计算和并发。并发问题通过解决线程安全的一系列 API 来解决;而异步计算,常见的使用是 Runnable 和 Callable 配合线程使用。
FutureTask 是基于 Runnable 实现的一个可取消的异步调用 API 。
基本使用
- Future 代表了异步计算的结果,通过 ExecutorService 的
Future<?> submit(Runnable task)
方法,作为返回值使用:
- FutureTask类是实现了Runnable的Future的实现,因此可以由Executor执行。例如,上述带有submit的构造可以替换为:
代码分析
继承关系
Future
Future 表示异步计算的结果。定义了用于检查计算是否完成、等待计算完成以及检索计算结果的能力。只有在计算完成后,才能使用 get 方法检索结果,在必要时会阻塞线程直到 Future 计算完成。取消是由 cancel 方法执行的。还提供了其他方法来确定任务是正常完成还是被取消。一旦计算完成,就不能取消计算。如果为了可取消性而使用 Future ,但又不想提供一个可用的结果,你可以声明形式 Future<?>
并返回 null 作为任务的结果。
在介绍 Future 中定义的能力之前,先了解一下它的用来表示 Future 状态内部类,和状态检索方法:
Future 的状态检索的默认实现是根据 isDone()
、isCancelled()
和不断轮询 get()
方法获取到的返回值判断的。
当 get()
正常返回结果时, state()
返回 State.SUCCESS
; 当抛出 InterruptedException
时,最终会操作所在的线程执行尝试中断的方法;抛出其他异常时,则返回 State.FAILED
。
Future 中定义的其他方法包括:
其中 resultNow()
和 exceptionNow()
是带有默认实现的:
- Future 仍在运行中,直接抛出 IllegalStateException 。
- 执行一个轮询,调用
get()
尝试返回计算结果,如果get()
抛出异常,则根据异常抛出不同消息的 IllegalStateException 或执行中断线程的操作。
- Future 仍在运行中,直接抛出 IllegalStateException 。
- Future 检查是否已取消,如果取消了抛出 IllegalStateException 。
- 执行轮询,调用
get()
方法,如果能够正常执行结束,也抛出 IllegalStateException ,消息是 "Task completed with a result" ;get()
若抛出 InterruptedException ,则执行线程中断操作;其他异常正常抛出。
这就是 Future 的全貌了。
RunnableFuture
RunnableFuture 接口同时实现了 Runnable 和 Future 接口 :
Runnable 接口是我们常用的用来实现线程操作的,可以说是十分熟悉也十分简单了。
这个接口代表了一个可以 Runnable 的 Future ,run 方法的成功执行代表着 Future 执行完成,并可以获取它的计算结果。
这个接口是 JDK 1.6 后续才有的。
FutureTask
FutureTask 是 RunnableFuture 的直接实现类,它代表了一个可取消的异步计算任务。根据我们对 Future 的分析和 Runnable 的熟悉,就可以理解它的作用了:可取消并可以检索运行状态的一个 Runnable ,配合线程使用可以中断线程执行。当任务没有执行完成时会造成阻塞。并且它还可以配合 Executor 使用。
状态
FutureTask 内部也定义了自己的状态:
FutureTask 的状态包括 7 种,最初为 NEW
,只有在 set、setException 和 cancel 方法中,运行状态才会转换为最终状态。在完成期间,状态可能为 COMPLETING
(当结果正在设置时) 或 INTERRUPTING
(仅当中断跑者以满足cancel(true)
)的瞬态值。
可能存在的状态转换是:
属性
下面分析一下它的属性:
内部类
先看一看这个 WaitNode ,这是一个 FutureTask 的内部类:
一个链表结构,用来对等待线程进行排序。
构造方法
最后是方法的分析,首先是构造方法:
FutureTask 接收一个 Callable 或一个 Runnable 作为参数,Runnable 会封装一下都保存到属性 callable
,然后更新 FutureTask 的状态为 NEW
。
从 Future 接口中实现的方法逐个分析:
检索 FutureTask 状态
取消操作
这里的 finishCompletion()
的作用是通过 LockSupport 唤醒等待的全部线程并从等待列表中移除,然后调用done()
,最后把 callable 置空。相当于取消成功后释放资源的操作。
done()
是个空实现,供子类去自定义的。
计算结果
这里涉及两个方法:awaitDone
方法和 report
方法 。
awaitDone 方法:
通过 CAS 和 LockSupport 的挂起/唤醒操作配合,阻塞当前线程,异步地等待计算结果。
这里有个 removeWaiter
方法,内部就是遍历 waiters
,删除超时和中断的等待线程。
当异步逻辑执行完成后,调用 report 方法:
这里用到了一个 outcome ,它是一个 Object 类型,作为返回结果,通过 set 方法可以对它进行设置:
立刻获取结果或异常
这两个方法都是通过 outcome
预设的返回值,返回预期的结果或异常。
run 方法组
最后是实现了 Runnable 的 run 方法:
这里涉及两个方法,第一个是 setException(ex)
:
另一个是 handlePossibleCancellationInterrupt 方法:
最后是 runAndReset 方法:
执行计算时不设置其结果,然后将该 future 重置为初始状态,如果计算遇到异常或被取消,则不这样做。这是为本质上执行多次的任务设计的。
run 和 runAndReset 都用到了一个 RUNNER
, 最后就来揭秘一下这个东西:
MethodHandles.lookup()
创建一个MethodHandles.Lookup
对象,该对象可以创建所有访问权限的方法,包括public
,protected
,private
,default
。
VarHandle
主要用于动态操作数组的元素或对象的成员变量。VarHandle
通过 MethodHandles
来获取实例,然后调用 VarHandle
的方法即可动态操作指定数组的元素或指定对象的成员变量。
到此这篇关于Java 多线程并发FutureTask的文章就介绍到这了,更多相关Java 多线程并发内容请搜索编程学习网以前的文章希望大家以后多多支持编程学习网!