摘要:在分析它的源碼之前我們需要先了解一些預(yù)備知識。因為接口沒有返回值所以為了與兼容我們額外傳入了一個參數(shù)使得返回的對象的方法直接執(zhí)行的方法然后返回傳入的參數(shù)。
前言
系列文章目錄
FutureTask 是一個同步工具類,它實現(xiàn)了Future語義,表示了一種抽象的可生成結(jié)果的計算。在包括線程池在內(nèi)的許多工具類中都會用到,弄懂它的實現(xiàn)將有利于我們更加深入地理解Java異步操作實現(xiàn)。
在分析它的源碼之前, 我們需要先了解一些預(yù)備知識。本篇我們先來看看FutureTask 中所使用到的接口:Runnable、Callable、Future、RunnableFuture以及所使用到的工具類Executors,Unsafe。
FutureTask所使用到的接口 Runnable接口在前面Thread類源碼解讀的系列文章中我們說過, 創(chuàng)建線程最重要的是傳遞一個run()方法, 這個run方法定義了這個線程要做什么事情, 它被抽象成了Runnable接口:
@FunctionalInterface public interface Runnable { public abstract void run(); }
但是, 可以發(fā)現(xiàn), 這個方法并沒有任何返回值.
如果我們希望執(zhí)行某種類型的操作并拿到它的執(zhí)行結(jié)果, 該怎么辦呢?
要從某種類型的操作中拿到執(zhí)行結(jié)果, 最簡單的方式自然是令這個操作自己返回操作結(jié)果, 則相較于run方法返回void,我們可以令一個操作返回特定類型的對象, 這種思路的實現(xiàn)就是Callable接口:
@FunctionalInterface public interface Callable{ V call() throws Exception; }
對比Callable接口與Runnable接口, 我們可以發(fā)現(xiàn)它們最大的不同點在于:
Callable有返回值
Callable可以拋出異常
關(guān)于有返回值這點,我們并不意外,因為這就是我們的需求,call方法的返回值類型采用的泛型,該類型是我們在創(chuàng)建Callable對象的時候指定的。
除了有返回值外,相較于Runnable接口,Callable還可以拋出異常,這點看上去好像沒啥特別的,但是卻有大用處——這意味著如果在任務(wù)執(zhí)行過程中發(fā)生了異常,我們可以將它向上拋出給任務(wù)的調(diào)用者來妥善處理,我們甚至可以利用這個特性來中斷一個任務(wù)的執(zhí)行。而Runnable接口的run方法不能拋出異常,只能在方法內(nèi)部catch住處理,喪失了一定的靈活性。
使用Callable接口解決了返回執(zhí)行結(jié)果的問題, 但是也帶來了一個新的問題:
如何獲得執(zhí)行結(jié)果?
有的同學(xué)可能就要說了, 這還不簡單? 直接拿不就好了, 看我的:
public static void main(String[] args) { CallablemyCallable = () -> "This is the results."; try { String result = myCallable.call(); System.out.println("Callable 執(zhí)行的結(jié)果是: " + result); } catch (Exception e) { System.out.println("There is a exception."); } }
這種方法確實可以, 但是它存在幾個問題:
call方法是在當(dāng)前線程中直接調(diào)用的, 無法利用多線程。
call方法可能是一個特別耗時的操作, 這將導(dǎo)致程序停在myCallable.call()調(diào)用處, 無法繼續(xù)運行, 直到call方法返回。
如果call方法始終不返回, 我們沒辦法中斷它的運行。
因此, 理想的操作應(yīng)當(dāng)是, 我們將call方法提交給另外一個線程執(zhí)行, 并在合適的時候, 判斷任務(wù)是否完成, 然后獲取線程的執(zhí)行結(jié)果或者撤銷任務(wù), 這種思路的實現(xiàn)就是Future接口:
Future接口Future接口被設(shè)計用來代表一個異步操作的執(zhí)行結(jié)果。你可以用它來獲取一個操作的執(zhí)行結(jié)果、取消一個操作、判斷一個操作是否已經(jīng)完成或者是否被取消
public interface Future{ V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); }
Future接口一共定義了5個方法:
get()
該方法用來獲取執(zhí)行結(jié)果, 如果任務(wù)還在執(zhí)行中, 就阻塞等待;
get(long timeout, TimeUnit unit)
該方法同get方法類似, 所不同的是, 它最多等待指定的時間, 如果指定時間內(nèi)任務(wù)沒有完成, 則會拋出TimeoutException異常;
cancel(boolean mayInterruptIfRunning)
該方法用來嘗試取消一個任務(wù)的執(zhí)行, 它的返回值是boolean類型, 表示取消操作是否成功.
isCancelled()
該方法用于判斷任務(wù)是否被取消了。如果一個任務(wù)在正常執(zhí)行完成之前被cancel掉了, 則返回true
isDone()
如果一個任務(wù)已經(jīng)結(jié)束, 則返回true。注意, 這里的任務(wù)結(jié)束包含了以下三種情況:
任務(wù)正常執(zhí)行完畢
任務(wù)拋出了異常
任務(wù)已經(jīng)被取消
關(guān)于cancel方法,這里要補充說幾點:
首先有以下三種情況之一的,cancel操作一定是失敗的:
任務(wù)已經(jīng)執(zhí)行完成了
任務(wù)已經(jīng)被取消過了
任務(wù)因為某種原因不能被取消
其它情況下,cancel操作將返回true。值得注意的是,cancel操作返回true并不代表任務(wù)真的就是被取消了,這取決于發(fā)動cancel狀態(tài)時任務(wù)所處的狀態(tài):
如果發(fā)起cancel時任務(wù)還沒有開始運行,則隨后任務(wù)就不會被執(zhí)行;
如果發(fā)起cancel時任務(wù)已經(jīng)在運行了,則這時就需要看mayInterruptIfRunning參數(shù)了:
如果mayInterruptIfRunning 為true, 則當(dāng)前在執(zhí)行的任務(wù)會被中斷
如果mayInterruptIfRunning 為false, 則可以允許正在執(zhí)行的任務(wù)繼續(xù)運行,直到它執(zhí)行完
這個cancel方法的規(guī)范看起來有點繞,現(xiàn)在不太理解沒關(guān)系,后面結(jié)合實例去看就容易弄明白了,我們將在下一篇分析FutureTask源碼的時候詳細說說FutureTask對這一方法的實現(xiàn)。
RunnableFuture 接口RunnableFuture接口人如其名, 就是同時實現(xiàn)了Runnable接口和Future接口:
public interface RunnableFutureextends Runnable, Future { void run(); }
我們下一篇開始分析FutureTask的源碼的時候就將看到,F(xiàn)utureTask實現(xiàn)了該接口,也就是相當(dāng)于它同時實現(xiàn)了Runnable接口和Future接口。
有的同學(xué)可能會對這個接口產(chǎn)生疑惑,既然已經(jīng)繼承了Runnable,該接口自然就繼承了run方法,為什么要在該接口的內(nèi)部再寫一個run方法?
單純從理論上來說,這里確實是沒有必要的,再多寫一遍,我覺得大概就是為了看上去直觀一點,便于文檔或者UML圖展示。
FutureTask所使用到的工具類 ExecutorsExecutors 是一個用于創(chuàng)建線程池的工廠類,關(guān)于線程池的概念,我們以后再說。這個類同時也提供了一些有用的靜態(tài)方法。
前面我們提到了Callable接口,它是JDK1.5才引入的,而Runnable接口在JDK1.0就有了,我們有時候需要將一個已經(jīng)存在Runnable對象轉(zhuǎn)換成Callable對象,Executors工具類為我們提供了這一實現(xiàn):
public class Executors { /** * Returns a {@link Callable} object that, when * called, runs the given task and returns the given result. This * can be useful when applying methods requiring a * {@code Callable} to an otherwise resultless action. * @param task the task to run * @param result the result to return * @paramthe type of the result * @return a callable object * @throws NullPointerException if task null */ public static Callable callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter (task, result); } /** * A callable that runs given task and returns given result */ static final class RunnableAdapter implements Callable { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; } } }
可以明顯看出來,這個方法采用了設(shè)計模式中的適配器模式,將一個Runnable類型對象適配成Callable類型。
因為Runnable接口沒有返回值, 所以為了與Callable兼容, 我們額外傳入了一個result參數(shù), 使得返回的Callable對象的call方法直接執(zhí)行Runnable的run方法, 然后返回傳入的result參數(shù)。
有的同學(xué)要說了, 你把result參數(shù)傳進去, 又原封不動的返回出來, 有什么意義呀?
這樣做確實沒什么意義, result參數(shù)的存在只是為了將一個Runnable類型適配成Callable類型.
Unsafe類對于并發(fā)編程來說是個很重要的類,如果你稍微看過J.U.C里的源碼(例如我們前面講AQS系列的文章里),你會發(fā)現(xiàn)到處充斥著這個類的方法調(diào)用。
這個類的最大的特點在于,它提供了硬件級別的CAS原子操作。
可能有的同學(xué)會覺得這并沒有什么了不起,CAS的概念都被說爛了。但是,CAS可以說是實現(xiàn)了最輕量級的鎖,當(dāng)多個線程嘗試使用CAS同時更新同一個變量時,只有其中的一個線程能成功地更新變量的值,而其他的線程將失敗。然而,失敗的線程并不會被掛起。
CAS操作包含了三個操作數(shù): 需要讀寫的內(nèi)存位置,進行比較的原值,擬寫入的新值。
在Unsafe類中,實現(xiàn)CAS操作的方法是: compareAndSwapXXX
例如:
public native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
obj是我們要操作的目標(biāo)對象
offset表示了目標(biāo)對象中,對應(yīng)的屬性的內(nèi)存偏移量
expect是進行比較的原值
update是擬寫入的新值。
所以該方法實現(xiàn)了對目標(biāo)對象obj中的某個成員變量(field)進行CAS操作的功能。
那么,要怎么獲得目標(biāo)field的內(nèi)存偏移量offset呢? Unsafe類為我們提供了一個方法:
public native long objectFieldOffset(Field field);
該方法的參數(shù)是我們要進行CAS操作的field對象,要怎么獲得這個field對象呢?最直接的辦法就是通過反射了:
Class> k = FutureTask.class; Field stateField = k.getDeclaredField("state");
這樣一波下來,我們就能對FutureTask的state屬性進行CAS操作了o( ̄▽ ̄)o
除了compareAndSwapObject,Unsafe類還提供了更為具體的對int和long類型的CAS操作:
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update); public native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);
從方法簽名可以看出,這里只是把目標(biāo)field的類型限定成int和long類型,而不是通用的Object.
最后,F(xiàn)utureTask還用到了一個方法:
public native void putOrderedInt(Object obj, long offset, int value);
可以看出,該方法只有三個參數(shù),所以它沒有比較再交換的概念,某種程度上就是一個賦值操作,即設(shè)置obj對象中offset偏移地址對應(yīng)的int類型的field的值為指定值。這其實是Unsafe的另一個方法putIntVolatile的有序或者有延遲的版本,并且不保證值的改變被其他線程立即看到,只有在field被volatile修飾并且期望被意外修改的時候使用才有用。
那么putIntVolatile方法的定義是什么呢?
public native void putIntVolatile(Object obj, long offset, int value);
該方法設(shè)置obj對象中offset偏移地址對應(yīng)的整型field的值為指定值,支持volatile store語義。由此可以看出,當(dāng)操作的int類型field本身已經(jīng)被volatile修飾時,putOrderedInt和putIntVolatile是等價的。
好了,到這里,基本需要用到的預(yù)備知識我們都學(xué)習(xí)完了,障礙已經(jīng)掃清,下一篇我們就可以愉快地看FutureTask的源碼了(?ˉ?ˉ?)
(完)
查看更多系列文章: 系列文章目錄
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/77316.html
摘要:本文的源碼基于。人如其名,包含了和兩部分。而將一個任務(wù)的狀態(tài)設(shè)置成終止態(tài)只有三種方法我們將在下文的源碼解析中分析這三個方法。將棧中所有掛起的線程都喚醒后,下面就是執(zhí)行方法這個方法是一個空方 前言 系列文章目錄 有了上一篇對預(yù)備知識的了解之后,分析源碼就容易多了,本篇我們就直接來看看FutureTask的源碼。 本文的源碼基于JDK1.8。 Future和Task 在深入分析源碼之前,我...
摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續(xù)更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因為寫作的時候發(fā)現(xiàn),為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...
摘要:零前期準(zhǔn)備文章異常啰嗦且繞彎。版本版本簡介是中默認的實現(xiàn)類,常與結(jié)合進行多線程并發(fā)操作。所以方法的主體其實就是去喚醒被阻塞的線程。本文僅為個人的學(xué)習(xí)筆記,可能存在錯誤或者表述不清的地方,有緣補充 零 前期準(zhǔn)備 0 FBI WARNING 文章異常啰嗦且繞彎。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 2 ThreadLocal 簡介 ...
閱讀 1618·2021-09-08 10:42
閱讀 3604·2021-08-11 10:23
閱讀 3959·2019-08-30 14:10
閱讀 2732·2019-08-29 17:29
閱讀 3090·2019-08-29 12:50
閱讀 637·2019-08-26 13:36
閱讀 3456·2019-08-26 11:59
閱讀 1487·2019-08-23 16:23