<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Thread, Runable, Callable 還傻傻分不清?

          共 7300字,需瀏覽 15分鐘

           ·

          2020-12-22 11:12

          Java技術(shù)棧

          www.javastack.cn

          關(guān)注閱讀更多優(yōu)質(zhì)文章



          轉(zhuǎn)載自公眾號:17coding技術(shù)博客

          一、Thread 與 Runnable

          1、創(chuàng)建線程的兩種方法

          在java中你怎么創(chuàng)建線程?相信你很快能夠想到繼承Thread類和實現(xiàn)Runnable接口這兩種方式。

          沒錯,java提供了這兩種方式來創(chuàng)建新的線程。

          網(wǎng)上也有各種文章介紹這兩種方式創(chuàng)建線程的區(qū)別,但是我們這里要講的是這兩種方式的關(guān)聯(lián)。

          先分別看看這兩種方式的代碼:

          1、繼承Thread類,重寫run方法

          public?class?MyThread?extends?Thread?{????
          ????
          ????@Override
          ????public?void?run()?{
          ????????System.out.println("我是繼承Thread類創(chuàng)建的線程喲");
          ????}????
          ????
          ????public?static?void?main(String[]?args)?{
          ????????MyThread?myThread?=?new?MyThread();
          ????????myThread.start();
          ????}
          }

          2、實現(xiàn)Runnable接口,實現(xiàn)run方法

          public?class?MyRunnable?implements?Runnable?{????
          ????
          ????@Override
          ????public?void?run()?{
          ????????System.out.println("我是實現(xiàn)Runnable接口創(chuàng)建的線程喲");
          ????}????
          ????
          ????public?static?void?main(String[]?args)?{
          ????????MyRunnable?myRunnable?=?new?MyRunnable();????????
          ????????new?Thread(myRunnable).start();
          ????}
          }

          通過上面的代碼我們不難發(fā)現(xiàn),第一種方式中,繼承了Thread類的子類通過重寫父類的run方法就可以實現(xiàn)線程的創(chuàng)建。

          而第二種方式中實現(xiàn)Runnable接口的類的對象可以作為一個參數(shù)傳遞到創(chuàng)建的thread對象中。那么Runnable為何方神圣?跟Thread類之間又有哪些不為人知的秘密?

          2、Thread與Runnable的關(guān)聯(lián)

          我們先看下Runnable的源碼:

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

          What? Runnable接口這么簡單?就一個run方法?

          是的,你沒有看錯,Runnable接口就這么簡單。如果沒有Thread類,那么Runnable接口就是普通得不能再普通的一個接口了,我們在代碼中實現(xiàn)這個接口,也做不了任何事情!但由于得到Thread類的“青睞”,這個接口就變得不一般了!

          那么Runnable接口得到Thread類的青睞,具體表現(xiàn)在哪呢?我們不妨先看看Thread類的定義

          public?class?Thread?implements?Runnable{}

          原來Thread是Runnable的一個實現(xiàn)類?。?!以程序人的第一直覺,那Thread自然應(yīng)該實現(xiàn)了run方法,我們繼續(xù)在源碼中搜尋!

          @Override
          public?void?run()?{????????
          ??if?(target?!=?null)?{
          ????????target.run();
          ??}
          }

          果不其然,Thread實現(xiàn)了run方法,并且有個判斷,當(dāng)target為null的時候什么也不做,否則執(zhí)行target的run方法,target又是什么呢?

          private?Runnable?target;

          target也是一個Runnable對象,那這個私有的字段在哪里賦值的呢?我們繼續(xù)尋找發(fā)現(xiàn)是在init方法里面進行賦值的,并且最終在構(gòu)造函數(shù)中調(diào)用了init方法,我們看看構(gòu)造函數(shù)的定義(Thread重載了多個構(gòu)造函數(shù))

          public?Thread(Runnable?target)?{
          ????init(null,?target,?"Thread-"?+?nextThreadNum(),?0);
          }

          這里我們能看到Thread的構(gòu)造函數(shù)支持Runnable的參數(shù),不知道到目前大家有沒有捋清楚Thread與Runnable的關(guān)系,我們再總結(jié)一下:

          1、Runnable原本平民,只是一個普通的接口。
          2、Thread實現(xiàn)了Runnable接口,并且實現(xiàn)了接口的run方法。
          3、Thread提供了重載的構(gòu)造函數(shù),接收Runnable類型的參數(shù)。在Thread重寫的run方法中對調(diào)用了構(gòu)造函數(shù)傳入的Runnable實現(xiàn)類的run方法。

          所以不管我們用哪種方式創(chuàng)建線程,都要實現(xiàn)或者重寫run方法!并且這個run方法都是實現(xiàn)的Runnable接口中的方法。這個run方法就是我們自定義的需要在線程中處理的一些邏輯!那這個run方法在哪里調(diào)用的呢?直接調(diào)用run方法可以創(chuàng)建新的線程么?為什么我們在代碼中都是調(diào)用的start方法去啟動一個線程?start方法與run方法有什么關(guān)聯(lián)?

          好奇心的驅(qū)使,我再次打開了源碼中的start方法一探究竟,發(fā)現(xiàn)這個方法中最主要的行為是調(diào)用了一個名為start0的方法。

          public?synchronized?void?start()?{
          ??……
          ??start0();
          ??……
          }

          那start0又是什么呢?

          private?native?void?start0();

          看到native關(guān)鍵字我們就應(yīng)該知道,這個方法是JVM的方法,具體的實現(xiàn)需要查看C的代碼!

          3、start方法與run方法的關(guān)聯(lián)

          鑒于自己C語言很爛,且多年沒有碰過了,但是又想弄清楚start方法與run方法的關(guān)聯(lián),于是在網(wǎng)上查找了相關(guān)的資料,下面做一下整理。

          推薦閱讀:多線程 start 和 run 方法的區(qū)別。

          參考資料:

          https://www.ibm.com/developerworks/cn/java/j-lo-processthread/

          在Thread 類的頂部,有個native的registerNatives本地方法,該方法主要的作用就是注冊一些本地方法供 Thread 類使用,如start0(),stop0() 等等,可以說,所有操作本地線程的本地方法都是由它注冊的 . 這個方法放在一個static語句塊中,這就表明,當(dāng)該類被加載到JVM中的時候,它就會被調(diào)用,進而注冊相應(yīng)的本地方法。

          private?static?native?void?registerNatives();????

          ??static?{
          ????registerNatives();
          ??}
          ??
          }??

          本地方法registerNatives是定義在Thread.c文件中的。T

          hread.c是個很小的文件,定義了各個操作系統(tǒng)平臺都要用到的關(guān)于線程的公用數(shù)據(jù)和操作

          JNIEXPORT?void?JNICALL
          ????Java_Java_lang_Thread_registerNatives?(JNIEnv?*env,?jclass?cls){
          ??????(*env)->RegisterNatives(env,?cls,?methods,?ARRAY_LENGTH(methods));
          ????}
          ????
          ????static?JNINativeMethod?methods[]?=?{
          ???????……
          {"start0",?"()V",(void?*)&JVM_StartThread},
          ???????{"stop0",?"("?OBJ?")V",?(void?*)&JVM_StopThread},
          ??????……
          };

          到此,可以容易的看出Java線程調(diào)用start的方法,實際上會調(diào)用到JVM_StartThread方法,那這個方法又是怎樣的邏輯呢。實際上,我們需要的是(或者說 Java 表現(xiàn)行為)該方法最終要調(diào)用Java線程的run方法,事實的確如此。在jvm.cpp中,有如下代碼段:

          JVM_ENTRY(void,?JVM_StartThread(JNIEnv*?env,?jobject?jthread))
          ??????……
          ?????native_thread?=?new?JavaThread(&thread_entry,?sz);
          ?????……

          這里JVM_ENTRY是一個宏,用來定義JVM_StartThread 函數(shù),可以看到函數(shù)內(nèi)創(chuàng)建了真正的平臺相關(guān)的本地線程,其線程函數(shù)是 thread_entry,代碼如下所示。

          static?void?thread_entry(JavaThread*?thread,?TRAPS)?{
          ??HandleMark?hm(THREAD);
          ???Handle?obj(THREAD,?thread->threadObj());
          ???JavaValue?result(T_VOID);
          ???JavaCalls::call_virtual(&result,obj,
          ???KlassHandle(THREAD,SystemDictionary::Thread_klass()),
          ???vmSymbolHandles::run_method_name(),
          ???vmSymbolHandles::void_method_signature(),THREAD);
          }

          可以看到調(diào)用了vmSymbolHandles::run_method_name方法,這是在 vmSymbols.hpp用宏定義的:

          class?vmSymbolHandles:?AllStatic?{
          ??…?????????template(run_method_name,"run")
          ??…
          }

          至于run_method_name是如何聲明定義的,因為涉及到很繁瑣的代碼細節(jié),本文不做贅述。感興趣的讀者可以自行查看JVM的源代碼。

          綜上所述,Java線程的創(chuàng)建調(diào)用過程如上圖所示,首先 , Java線程的start方法會創(chuàng)建一個本地線程(通過調(diào)用JVM_StartThread),該線程的線程函數(shù)是定義在jvm.cpp中的thread_entry,由其再進一步調(diào)用run方法。

          可以看到Java線程的run方法和普通方法其實沒有本質(zhì)區(qū)別,直接調(diào)用run方法不會報錯,但是卻是在當(dāng)前線程執(zhí)行,而不會創(chuàng)建一個新的線程。關(guān)注公眾號Java技術(shù)棧在后臺回復(fù)面試獲取Java并發(fā)系列面試題及答案,我都整理好了。

          二、FutureTask、Callable 與 Runnable

          1、創(chuàng)建能獲取結(jié)果的異步線程

          上面我們了解了創(chuàng)建線程創(chuàng)建的兩種方式,但是我們也能看到,通過runnable方式創(chuàng)建的線程無法獲取返回值,如果我們需要異步執(zhí)行某個操作,并且得到返回的結(jié)果,可能上面的兩種創(chuàng)建線程的方式就不適用啦(也不是說不可以,比如通過共享變量等方式,但是使用起來會比較麻煩)!在java中提供一種比較方便的方式,那就是使用FutureTask類,我們先看看使用方式:

          public?class?MyFutrueTask?implements?Callable?{

          ????@Override
          ????public?String?call()?throws?Exception?{
          ????????System.out.println("我是Future模式的線程啦");

          ????????return?"Future模式的線程結(jié)束啦";
          ????}

          ????public?static?void?main(String[]?args)?throws?TimeoutException,?ExecutionException,?InterruptedException?{
          ????????FutureTask?futureTask?=?new?FutureTask(new?MyFutrueTask());
          ????????new?Thread(futureTask).start();

          ????????String?result?=?futureTask.get(2000,?TimeUnit.MILLISECONDS);

          ????????System.out.println(result);
          ????}

          }

          主類實現(xiàn)了一個名為Callable的接口,并且實現(xiàn)了接口的call方法,具體的業(yè)務(wù)邏輯就在call方法中實現(xiàn)!實現(xiàn)Callable接口的類的對象可以作為一個參數(shù)傳遞到創(chuàng)建的FutureTask對象的構(gòu)造函數(shù)中,而FutureTask類的對象又作為一個參數(shù)傳遞到Thread對象的構(gòu)造函數(shù)中……

          根據(jù)上面我們對Thread和Runnable的了解,能夠作為參數(shù)傳入到Thread構(gòu)造函數(shù)的對象,一定是實現(xiàn)了Runnable接口的!那是不是說FutureTask對象就應(yīng)該是一個Runnable的實現(xiàn)類呢?

          2、FutureTask實現(xiàn)

          我們先看看FutureTask類的定義

          public?class?FutureTask?implements?RunnableFuture

          FutureTask是一個泛型類,并且實現(xiàn)了RunnableFutrue泛型接口,我們繼續(xù)跟進

          public?interface?RunnableFuture?extends?Runnable,?Future?{???
          ????void?run();
          }

          RunnableFutrue接口繼承了Runnable接口,這也就是說FutureTask間接的實現(xiàn)了Runnable接口,F(xiàn)utureTask也是Runnable的一個實現(xiàn)類,那也就必須要實現(xiàn)run方法,還能夠?qū)嵗龑ο髠魅隩hread對象的構(gòu)造后函數(shù)!RunnableFutrue接口還繼承了另外的一個名為Futrue的泛型接口,我們看看該接口的定義

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

          根據(jù)這些方法的命名能夠看出來,F(xiàn)utureTask實現(xiàn)了Future接口后,就應(yīng)該擁有了取消線程、判斷線程運行狀態(tài)、獲取結(jié)果等功能!

          我們整體看一下FutureTask的類圖:

          FutureTask類中對這些接口的具體實現(xiàn)是怎么樣的呢?我們可以到FutureTask類中一探究竟,先瞅瞅構(gòu)造函數(shù)

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

          構(gòu)造函數(shù)接收一個Callable類型的參數(shù),Callable是一個泛型類型的接口,該接口只有一個名為call的方法。本來這也只是一個普通的接口,但由于收到FutrueTask的“青睞”,這個接口變得不一般了!

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

          乍一看是不是覺得跟Runnable接口很像呢?但是Callable接口的call方法有返回值!Callable、Runnable、FutureTask之間是怎么關(guān)聯(lián)起來的呢?我們上面有說了FutureTask是Runnable的一個實現(xiàn)類,那FutureTask是不是應(yīng)該也實現(xiàn)了run方法呢?我們跟進一下代碼:

          public?void?run()?{
          ????......
          ????try?{
          ????????Callable?c?=?callable;
          ????????if?(c?!=?null?&&?state?==?NEW)?{
          ????????????V?result;
          ????????????boolean?ran;
          ????????????try?{
          ????????????????result?=?c.call();
          ????????????????ran?=?true;
          ????????????}?catch?(Throwable?ex)?{
          ????????????????......
          ????????????}
          ????????????if?(ran)
          ????????????????set(result);
          ????????}
          ????}?finally?{
          ????????......
          ????}
          }

          Run方法的實現(xiàn)也比較簡單,上述代碼只保留了需要關(guān)注的代碼。

          在run方法中調(diào)用了構(gòu)造函數(shù)傳入的Callable實現(xiàn)類的call方法,并且用result的變量接收方法的返回值,最后調(diào)用set方法將返回結(jié)果設(shè)置到類的屬性,由于FutureTask實現(xiàn)了Future接口,所以也就有了獲取返回值以及判斷線程是否運行完成、取消的能力!

          也就是說,我們自定義Callable實現(xiàn)類的call方法,最終會在FutureTask類(也就是Runnable)的run方法中執(zhí)行!而Runnable中的run方法,最終又會在Thread類的run方法中執(zhí)行。






          關(guān)注Java技術(shù)棧看更多干貨



          戳原文,獲取精選面試題!
          瀏覽 72
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日韩视频高清 | 黄色片视频免费在线观看 | 国产精品免费视频观看 | 色骚综合 | 亚洲无吗免费在线观看 |