开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情
上一篇文章,我们一起学习了 Java 的有返回值的异步模型 :重拾 Java 高并发:两种异步模型(二) 。主要是采用 Future 和 FutureTask 来实现。接下来,我们从源码的角度,深入理解下 Future 接口。
1. Future 接口全貌
Future是JDK1.5新增的异步编程接口,其源代码如下所示:
我们简单看下这五个方法的含义:
-
cancel:取消任务的执行,接收一个boolean类型的参数,成功取消任务,则返回true,否则返回false。当任务已经完成,已经结束或者因其他原因不能取消时,方法会返回false,表示任务取消失败。当任务未启动调用了此方法,并且结果返回true(取消成功),则当前任务不再运行。如果任务已经启动,会根据当前传递的boolean类型的参数来决定是否中断当前运行的线程来取消当前运行的任务。
-
isCancelled:判断任务在完成之前是否被取消,如果在任务完成之前被取消,则返回true;否则,返回false。这里需要注意一个细节:只有任务未启动,或者在完成之前被取消,才会返回true,表示任务已经被成功取消。其他情况都会返回 false。
-
isDone:判断任务是否已经完成,如果任务正常结束、抛出异常退出、被取消,都会返回true,表示任务已经完成。
-
get():当任务完成时,直接返回任务的结果数据;当任务未完成时,等待任务完成并返回任务的结果数据。
-
get(long, TimeUnit):当任务完成时,直接返回任务的结果数据;当任务未完成时,等待任务完成,并设置了超时等待时间。在超时时间内任务完成,则 返回结果;否则,抛出TimeoutException异常。
2. RunnableFuture 接口
RunnableFuture接口继承了 Future 接口,也继承了java.lang.Runnable接口:
3. FutureTask 类
FutureTask类是RunnableFuture接口的一个非常重要的实现类,它实现了RunnableFuture接口、Future接口和Runnable接口的所有方法。
(1) FutureTask 类中的变量与常量
- 在 FutureTask 类中首先定义了一个状态变量 state ,这个变量使用了 volatile 关键字修饰。volatile 关键字通过内存屏障和禁止重排序优化来实现线程安全;然后定义了几个任务运行时的状态常量:
- 另外,还定义了其他几个成员变量
(2)构造方法
(3)是否取消、是否完成
(4)取消任务方法
-
首先判断任务的状态和CAS的操作结果,如果任务的状态不等于 NEW 或者 CAS 的操作返回 false,则直接返回 false,表示任务取消失败
-
在try代码块中,首先判断是否可以中断当前任务所在的线程来取消任务的运行。如果可以中断当前任务所在的线程,则以一个Thread临时变量来指向运行任务的线程,当指向的变量不为空时,调用线程对象的interrupt()方法来中断线程的运行,最后将线程标记为被中断的状态。
-
可以看到在 finallly 代码块中调用了 finishCompletion() 方法,表示结束任务的运行
- 首先定义一个for循环,循环终止因子为waiters为null
- 在循环中,判断 CAS 操作是否成功,如果成功进行 if 条件中的逻辑
- 首先,定义一个for自旋循环,在自旋循环体中,唤醒WaitNode堆栈中的线程,使其运行完成
- 循环退出后,执行 done 方法(done 方法是一个空的方法体,交由子类来实现具体的业务逻辑。)
(5)get 方法获取结果
没参数的get()方法为当任务未运行完成时,会阻塞,直到返回任务结果。有参数的get()方法为当任务未运行完成,并且等待时间超出了超时时间,会TimeoutException异常。
(6)set 方法
通过源码可以看出,set()方法与setException()方法整体逻辑几乎一样,只是在设置任务状态时一个将状态设置为NORMAL,一个将状态设置为EXCEPTIONAL。
(7)run 方法
- 在 run() 方法中,如果当前状态不是NEW,或者CAS操作返回的结果为false,则直接返回,不再执行后续逻辑。
- 在try代码块中,将成员变量callable赋值给一个临时变量c,判断临时变量不等于null,并且任务状态为NEW,则调用 Callable接口的call()方法,并接收结果数据。并将ran变量设置为true。当程序抛出异常时,将接收结果的变量设置为null,ran变量设置为false,并且调用setException()方法将任务的状态设置为EXCEPTIONA。接下来,如果ran变量为true,则调用set()方法。
- 将runner设置为null,如果任务的当前状态大于或者等于INTERRUPTING,也就是线程被中断了。则调用 handlePossibleCancellationInterrupt()方法,当任务的状态为INTERRUPTING时,使用while()循环,条 件为当前任务状态为INTERRUPTING,将当前线程占用的CPU资源释放,也就是说,当任务运行完成后,释放线程所占用的资源。
4. 小结
Future 的实现中用了许多 CAS 算法,大家可以好好理解源码,体会其中的奥秘!