【原創】Java併發編程系列36 | FutureTask

  公衆號改版後文章亂序推薦,但願你能夠點擊上方「Java進階架構師」,點擊右上角,將咱們設爲星標」!這樣纔不會錯過每日進階架構文章呀。java

  

  2020年Java原創面試題庫連載中mysql

  (共18篇)
web

  【032期】JavaEE面試題(四)Spring(2)
面試

  

  線程池源碼中出現了不少Callable、Future、FutureTask等之前沒介紹過的接口,尤爲是線程池提交任務時老是把任務封裝成FutureTask,今天就來爲你們解惑:sql

  Runnable、Callable、Future、FutureTask緩存

  FutureTask類結構性能優化

  FutureTask狀態數據結構

  執行任務 run()方法架構

  獲取任務返回值 get()方法併發

  取消任務 cancel()方法

  1. Runnable、Callable、Future、FutureTask 1.1 Runnable

  Runnable接口只有一個run方法,而run方法的返回值是void,因此線程執行完以後沒有返回值。

  public interface Runnable {
public abstract void run();
}


1.2 Callable

  在不少場景下,咱們經過線程來異步執行任務以後,但願獲取到任務的執行結果。好比RPC框架中,須要異步獲取任務返回值。這種狀況下,Runnable沒法獲取返回值就沒法知足需求了,所以Callable就出現了。

  Callable也是一個接口,也只有一個call()方法,不一樣的是Callable的call()方法有是有返回值的,返回值的類型是一個泛型,泛型由建立Callable對象時指定。

  public interface Callable {
V call() throws Exception;
}


1.3 Future

  要想得到Callable的返回值就須要用到Future接口。Futrue能夠監視和控制Callable任務的執行狀況,如對執行結果進行取消、查詢是否完成、獲取結果等。

  如:當一個任務經過線程池的submit()方法提交到線程池後,線程池會返回一個Future類型的對象,咱們能夠經過Future對象來獲取任務在線程池中的狀態。

  public interface Future {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}







  cancel方法:用來取消任務,若是取消任務成功則返回true,若是取消任務失敗則返回false。
mayInterruptIfRunning參數用來表示是否須要中斷線程,若是傳true,表示須要中斷線程,那麼就會將任務的狀態設置爲INTERRUPTING;若是爲false,那麼就會將任務的狀態設置爲CANCELLED(關於任務的狀態INTERRUPTING和CANCELLED後面會說明)

  isCancelled方法:表示任務是否被取消成功,若是在任務正常完成前被取消成功,則返回 true

  isDone方法:表示任務是否已經完成,若任務完成,則返回true

  get()方法:用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回。

  get(long timeout, TimeUnit unit)方法:獲取執行結果,若是在指定時間內,還沒獲取到結果,就直接返回null。

  舉例:Future獲取Callable任務的返回值

  public class FutureExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService threadPool = Executors.newCachedThreadPool();
Future future = threadPool.submit(new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "結果";
}
});
System.out.println("Callable返回值=" + future.get());
}
}












  輸出結果:

  Callable返回值=結果
1.4 FutureTask

  FutureTask是Runnable和Future的實現類,既能夠做爲Runnable被線程執行,又能夠做爲Future獲得Callable的返回值。

  當線程池調用submit()方法來向線程池中提交任務時,不管提交的是Runnable類型的任務,仍是提交的是Callable類型的任務,最終都是將任務封裝成一個FutureTask對象,咱們能夠經過這個FutureTask對象來獲取任務在線程池中的狀態。

  public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
// 調用newTaskFor()將Callable任務封裝成一個FutureTask
RunnableFuture ftask = newTaskFor(task);
// 執行任務
execute(ftask);
return ftask;
}






  // newTaskFor
protected RunnableFuture newTaskFor(Callable callable) {
// 直接new一個FutureTask對象
return new FutureTask(callable);
}




2. FutureTask類結構public class FutureTask implements RunnableFuture {
/** state變量用來保存任務的狀態 */
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;








  /** 提交的任務,Runnable類型的任務會經過Executors.callable()來轉變爲Callable */
private Callable callable;
/** 用來保存Callable的call()方法的返回值 */
private Object outcome;
/** 執行Callable任務的線程 **/
private volatile Thread runner;
/**
* 任務未完成時,調用get方法獲取結果的線程會阻塞等待
* waiters用於保存這些線程
*/
private volatile WaitNode waiters;









  static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
}





3. FutureTask狀態

  FutureTask任務的狀態以下:

  // 任務的初始狀態,當新建一個FutureTask任務時,state值默認爲NEW
private static final int NEW = 0;
// 任務處於完成中,也就是正在執行還未設置返回值
private static final int COMPLETING = 1;
// 任務正常被執行完成,並將任務的返回值賦值給outcome屬性以後
private static final int NORMAL = 2;
// 任務出了異常,並將異常對象賦值給outcome屬性以後
private static final int EXCEPTIONAL = 3;
// 調用cancle(false),任務被取消了
private static final int CANCELLED = 4;
// 調用cancle(true),任務中斷,可是在線程中斷以前
private static final int INTERRUPTING = 5;
// 調用cancle(true),任務中斷,可是在線程中斷以後
private static final int INTERRUPTED = 6;













  狀態變化以下圖:

  4. 執行任務run()

  執行future.callable.call(),執行任務;

  執行成功,設置結果outcome;

  逐個喚醒waiters中的線程去獲取執行結果。

  public void run() {
/*
* 1. 不是NEW狀態,不能執行
* 2. 設置runner失敗,不能執行
*/
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();// 真正執行任務
ran = true;// 執行成功,設置執行成功標誌
} catch (Throwable ex) {
result = null;
ran = false;// 有異常,執行失敗
setException(ex);// 設置異常
}
// 若是執行成功,則設置返回結果
if (ran)
set(result);
}
} finally {
runner = null;// 不管是否執行成功,把runner設置爲null
int s = state;
// 處理中斷
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
































  /**
* 設置執行結果
*/
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {// 執行完成,設置COMPLETING狀態
outcome = v;// 設置執行結果
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 設置完結果,設置NORMAL狀態
finishCompletion();// 逐個喚醒waiters中的線程去獲取執行結果
}
}









5. 獲取任務返回值get()方法

  任務狀態爲NORMAL,直接返回執行結果;

  任務狀態爲COMPLETING,線程yield()讓出CPU,由於COMPLETING到NORMAL只須要很短的時間,get線程讓出CPU的短暫時間,任務狀態就是從COMPLETING變成了NORMAL;

  任務狀態爲NEW,將get線程阻塞,若是設置了超時,阻塞至超時時間;若是沒有設置超時,會一直阻塞直到任務完成後喚醒。

  public V get() throws InterruptedException, ExecutionException {
int s = state;
// 若是狀態處於NEW或者COMPLETING狀態,表示任務尚未執行完成,awaitDone()等待
if (s <= COMPLETING)
s = awaitDone(false, 0L);// 下文詳解
// 返回結果,下文詳解
return report(s);
}






  /**
* 返回執行結果
*/
private V report(int s) throws ExecutionException {
Object x = outcome;
// 任務正常結束時,返回outcome
if (s == NORMAL)
return (V)x;
// 任務被取消了,拋出CancellationException
if (s >= CANCELLED)
throw new CancellationException();
// 這裏只能第EXCEPTIONAL狀態,表示在執行過程當中出現了異常,拋出ExecutionException。
throw new ExecutionException((Throwable)x);
}












  /**
* 處於NEW或者COMPLETING狀態時,get線程等待
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// ......
for (;;) {
// ......
// 任務處於COMPLETING中,就讓當前線程先暫時放棄CPU的執行權
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// ......
// 若是設置了超時,阻塞至超時時間
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
// 等待一段時間
LockSupport.parkNanos(this, nanos);
}
else
// 若是沒有設置超時,會一直阻塞,直到被中斷或者被喚醒
LockSupport.park(this);
}
}


























6. 取消任務 cancel()

  將任務狀態設置成INTERRUPTING/INTERRUPTED/CANCELLED狀態就表示取消了線程,由於在這些狀態下任務的run方法是不能執行的。

  public boolean cancel(boolean mayInterruptIfRunning) {
/*
* 如下狀況不能取消任務:
* 1. 當前任務不是NEW狀態,已經被執行了,不能取消
* 2. 當前任務尚未執行,state == NEW,可是CAS設置狀態失敗,不能取消
*/
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
// 中斷
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();// 中斷線程
} finally { // final state
// 中斷以後,設置INTERRUPTED狀態
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();// 喚醒waiters中的線程去獲取執行結果
}
return true;
}


























  以前,給你們發過三份Java面試寶典,此次新增了一份,目前總共是四份面試寶典,相信在跳槽前一個月按照面試寶典準備準備,基本沒大問題。

  《java面試寶典5.0》(初中級)

  《350道Java面試題:整理自100+公司》(中高級)

  《資深java面試寶典-視頻版》(資深)

  《Java[BAT]面試必備》(資深)

  分別適用於初中級,中高級資深級工程師的面試複習。

  內容包含java基礎、javaweb、mysql性能優化、JVM、鎖、百萬併發、消息隊列,高性能緩存、反射、Spring全家桶原理、微服務、Zookeeper、數據結構、限流熔斷降級等等。

  獲取方式:點「在看」,V信關注上述Java最全面試題庫號並回復【面試】便可領取,更多精彩陸續奉上。

  看到這裏,證實有所收穫

相關文章
相關標籤/搜索