欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

FutureTask為何單個任務僅執(zhí)行一次原理解析

 更新時間:2023年11月15日 09:42:21   作者:zhongh?Jim  
這篇文章主要為大家介紹了FutureTask為何單個任務僅執(zhí)行一次原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

前幾天會員領取情況查詢的接口SQL查詢超時出故障了,因為有個用戶買的會員有點多(哈哈),其實是 數(shù)據(jù)量大 + 祖?zhèn)鞔a邏輯冗長

嘗試的解決方案:

SQL:檢查了一下,單個SQL的耗時其實不算大,也能接受,不需要改動,主要原因是后端邏輯冗長

FutureTask獲取線程的執(zhí)行結果:將1次大查詢劃分為多次小查詢同時進行,提高接口響應速度。且一個FutureTask僅執(zhí)行一次,不會出現(xiàn)重復的查詢

經(jīng)過權衡,我們選擇了后者

一、FutureTask用法

解決方案要用到線程池搭配FutureTask,這里我們就不用了,簡化點

public class Test {
    //計算結果
    int count=0;
    @Test
    public void test(){
        try{  
            FutureTask<Integer> futureTask=new FutureTask<>(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return 1;
                }
            });
            //把FutureTask放入線程中,線程會運行FutureTask的run()代碼塊
            Thread t1=new Thread(futureTask);
            t1.start();
            //獲取計算的結果,是一個阻塞等待返回的方法
            count+=futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //最后結果: 1
        System.out.println(count);    
    }
}

這里用了構造方法public FutureTask(Callable<V> callable)讓FutureTask持有Callable接口的實例

用到try-catch是由于futureTask.get()方法是一個阻塞等待的過程,途中如果被中斷會拋中斷異常,別的異常都會以ExecutionException執(zhí)行異常的形式拋出

二、(重要)FutureTask的任務僅執(zhí)行一次,為何?

FutureTask的run()代碼塊僅執(zhí)行一次!請看注釋

/**
    執(zhí)行結果(全局變量), 有2種情況:
    1. 順利完成返回的結果
    2. 執(zhí)行run()代碼塊過程中拋出的異常
*/
private Object outcome; 
//正在執(zhí)行run()的線程, 內存可被其他線程可見
private volatile Thread runner;
???????public void run() {
    /**
        FutureTask的run()僅執(zhí)行一次的原因:
        1. state != NEW表示任務正在被執(zhí)行或已經(jīng)完成, 直接return
        2. 若state==NEW, 則嘗試CAS將當前線程 設置為執(zhí)行run()的線程,如果失敗,說明已經(jīng)有其他線程 先行一步執(zhí)行了run(),則當前線程return退出
    */
    if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))
        return;
    try {
        //持有Callable的實例,后續(xù)會執(zhí)行該實例的call()方法
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            }catch (Throwable ex) {
                result = null;
                ran = false;
                //執(zhí)行中拋的異常會放入outcome中保存
                setException(ex);
            }
            if (ran)
                //若無異常, 順利完成的執(zhí)行結果會放入outcome保存
                set(result);
        }
    }finally {
        // help GC 
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

執(zhí)行run()的代碼塊之后,其他線程如何拿到FutureTask的執(zhí)行結果?下面的get()方法可以做到

三、get()獲取結果

public V get() throws InterruptedException, ExecutionException {    
       int s = state;
       //COMPLETING: 正在完成的狀態(tài);  s <= COMPLETING就是未完成
    if (s <= COMPLETING)
        //不計時等待,結束等待的條件只有【完成】、【被中斷】、【被取消】、【拋其他異常(不包括中斷異常、取消異常)】
        s = awaitDone(false, 0L);
    return report(s);    
}

這里提一下線程執(zhí)行的狀態(tài) :

private volatile int state;
//線程創(chuàng)建狀態(tài)
private static final int NEW = 0;
//完成(**一個瞬時的標記**)   
private static final int COMPLETING = 1;
//正常完成狀態(tài)
private static final int NORMAL = 2;
//執(zhí)行過程出現(xiàn)異常
private static final int EXCEPTIONAL = 3;
//執(zhí)行過程中被取消
private static final int CANCELLED = 4;
//線程執(zhí)行被中斷(**一個瞬時的標記**)
private static final int INTERRUPTING = 5;
//線程執(zhí)行被中斷的狀態(tài)
private static final int INTERRUPTED = 6;

volatile保證了線程執(zhí)行的狀態(tài)改變之后會刷新到內存中,被其他線程可見

如果線程還處于未完成的狀態(tài),即s <= COMPLETING,就會進入等待狀態(tài),調用awaitDone(false, 0L)方法

get為何阻塞等待?

/**
    @param timed 若是true則為定時等待,超時后會結束等待,并返回當前狀態(tài)state
    @param nanos 如果是定時等待即第一個入?yún)imed=true的話,會設置對應的等待時長
*/
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    //等待的最后期限
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    //進入無限循環(huán)的等待狀態(tài),只有【完成】、【被取消】、【異?!俊ⅰ局袛唷?、【超時】這五種情況才會結束等待
    for (;;) {
        if (Thread.interrupted()) {
            //線程執(zhí)行被中斷,則移除等待結點并拋出異常
            removeWaiter(q);
            throw new InterruptedException();
        }
         int s = state;
        //【完成】、【被取消】、【拋其他異?!康臓顟B(tài)都會 在這 結束等待
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        //子線程處于任務完成的瞬時狀態(tài),要等一會才能拿到執(zhí)行結果
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        else if (q == null)
            q = new WaitNode();
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
        else if (timed) {
            //設置定時等待并且已經(jīng)超時了
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);       
    }
}

詳細的注釋在代碼中,請耐心看一下。

簡單來說,能結束等待的條件只有5個:

  • 完成
  • 被中斷
  • 設置定時等待并超時
  • 被取消
  • 拋了其他異常,比如RuntimeException,這里的其他異常既不是中斷異常,也不是取消異常

調用futureTask.get()的等待方式有2種,分為定時等待和 不計時等待:

  • timed=true是定時等待,會創(chuàng)建等待結點q = new WaitNode();并放在棧頂(隊列頭部),然后掛起。結束等待的條件(滿足任一即可)是【完成】、【被中斷】、【被取消】、【拋其他異?!俊ⅰ境瑫r】 。
  • timed=false是不計時等待,創(chuàng)建等待結點后會一直掛起,只有【完成】、【被中斷】、【被取消】、【拋其他異?!?/li>

在等待結束之前,LockSupport.park(this);表示線程會被一直掛起,不再繼續(xù)無限循環(huán)占用CPU。

解除掛起的條件是state > COMPLETING,然后調用finishCompletion()方法去讓線程解除掛起并回到awaitDone()做最后一次循環(huán)后return state

從get中返回結果report(int s)

/*正常的計算結果 or 拋出的異常 都會作為outcome*/
private Object outcome;
private V report(int s) throws ExecutionException {
    Object x = outcome;
    //正常完成
    if (s == NORMAL)
        return (V)x;
    //執(zhí)行的過程中【被取消】
    if (s >= CANCELLED)
        throw new CancellationException();
    /**
        這里拋的是執(zhí)行過程中發(fā)生的其他異常,既不是【中斷異?!?也不是【被取消異?!?
        比如發(fā)生了RuntimeException之類的就會在這拋
    */    
    throw new ExecutionException((Throwable)x);
}

report(int s)是執(zhí)行get()獲取結果的最后一步

看到這可能有朋友暈了,我把get()內部的流程梳理一下:

若要等待計算結果:get() -> awaitDone() -> report(),共3步
不用等待:get() -> report() ,僅2步

四、FutureTask是如何拿到線程執(zhí)行的結果?

主要 有賴于FutureTask類內部的Callable接口

只有Callable接口能拿到線程的返回值,下面來看下FutureTask的構造函數(shù)

public class FutureTask<V> implements RunnableFuture<V> {
    //執(zhí)行任務并返回結果
    private Callable<V> callable;
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        //新建狀態(tài)
        this.state = NEW;
    }
}

其實Callable 接口是沒法 作為創(chuàng)建線程new Thread(Runnable target)的入?yún)⒌?,只有借助FutureTask類才能被線程執(zhí)行,因為FutureTask實現(xiàn)了Runnable 接口

有興趣的可以看一下Future接口的關系圖(這里拿了大佬的圖,侵刪)

FutureTask類最終實現(xiàn)了Future接口和Runnable接口,可作為new Thread(Runnable target)的入?yún)arget來創(chuàng)建線程

五、FutureTask可能的執(zhí)行過程

順利完成 :NEW -> COMPLETING -> NORMAL ,即新建->正在完成 ->正常

NEW -> COMPLETING -> EXCEPTIONAL, 執(zhí)行過程出現(xiàn)了異常

被取消:NEW -> CANCELLED

NEW -> INTERRUPTING -> INTERRUPTED,新建 ->正在被中斷 ->中斷完成

六、列舉一下FutureTask的特性和應用場景

特性:

  • 異步執(zhí)行,可執(zhí)行多次(通過runAndReset()方法),也可僅執(zhí)行一次(執(zhí)行run()即可)
  • 可獲取線程執(zhí)行結果

應用場景:

  • 長時間運行的任務,包含遠程調用的任務
  • 數(shù)據(jù)量大的查詢,劃分為多個小查詢,每個FutureTask 僅執(zhí)行一次 的特性能有效避免重復的查詢
  • 計算密集型的任務

以上就是FutureTask為何單個任務僅執(zhí)行一次原理解析的詳細內容,更多關于FutureTask單任務執(zhí)行一次的資料請關注腳本之家其它相關文章!

相關文章

  • 關于Spring Boot動態(tài)權限變更問題的實現(xiàn)方案

    關于Spring Boot動態(tài)權限變更問題的實現(xiàn)方案

    這篇文章主要介紹了Spring Boot動態(tài)權限變更實現(xiàn)的整體方案使用session作為緩存,結合AOP技術進行token認證和權限控制,本文給大家介紹的非常詳細,需要的朋友參考下吧
    2021-06-06
  • Spring整合MyBatis圖示過程解析

    Spring整合MyBatis圖示過程解析

    這篇文章主要介紹了Spring整合MyBatis圖示過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • springBoot 整合ModBus TCP的詳細過程

    springBoot 整合ModBus TCP的詳細過程

    ModBus是一種串行通信協(xié)議,用于從儀器和控制設備傳輸信號到主控制器或數(shù)據(jù)采集系統(tǒng),它分為主站和從站,主站獲取和編寫數(shù)據(jù),從站則是設備,本文給大家介紹springBoot 整合ModBus TCP的詳細過程,感興趣的朋友一起看看吧
    2025-01-01
  • 分析講解Java?Random類里的種子問題

    分析講解Java?Random類里的種子問題

    Random類中實現(xiàn)的隨機算法是偽隨機,也就是有規(guī)則的隨機。在進行隨機時,隨機算法的起源數(shù)字稱為種子數(shù)(seed),在種子數(shù)的基礎上進行一定的變換,從而產生需要的隨機數(shù)字
    2022-05-05
  • idea創(chuàng)建SpringBoot項目及注解配置相關應用小結

    idea創(chuàng)建SpringBoot項目及注解配置相關應用小結

    Spring Boot是Spring社區(qū)發(fā)布的一個開源項目,旨在幫助開發(fā)者快速并且更簡單的構建項目,Spring Boot框架,其功能非常簡單,便是幫助我們實現(xiàn)自動配置,本文給大家介紹idea創(chuàng)建SpringBoot項目及注解配置相關應用,感興趣的朋友跟隨小編一起看看吧
    2023-11-11
  • 深入理解Java中的final關鍵字_動力節(jié)點Java學院整理

    深入理解Java中的final關鍵字_動力節(jié)點Java學院整理

    Java中的final關鍵字非常重要,它可以應用于類、方法以及變量。這篇文章中我將帶你看看什么是final關鍵字以及使用final的好處,具體內容詳情通過本文學習吧
    2017-04-04
  • 一篇文章帶你了解mybatis的動態(tài)SQL

    一篇文章帶你了解mybatis的動態(tài)SQL

    這篇文章主要為大家介紹了mybatis的動態(tài)SQL?,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • Java內存模型JMM與volatile

    Java內存模型JMM與volatile

    這篇文章主要介紹了Java內存模型JMM與volatile,Java內存模型是一種抽象的概念,并不真實存在,它描述的是一組規(guī)則或規(guī)范,定義了程序中各個變量的訪問方式
    2022-07-07
  • 如何對quartz定時任務設置結束時間

    如何對quartz定時任務設置結束時間

    這篇文章主要介紹了如何對quartz定時任務設置結束時間問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java?常量池詳解之class文件常量池?和class運行時常量池

    Java?常量池詳解之class文件常量池?和class運行時常量池

    這篇文章主要介紹了Java?常量池詳解之class文件常量池?和class運行時常量池,常量池主要存放兩大類常量:字面量,符號引用,本文結合示例代碼對java class常量池相關知識介紹的非常詳細,需要的朋友可以參考下
    2022-12-12

最新評論