乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      必須要理清的Java線程池

       Bladexu的文庫 2019-03-10
      作者:騎小豬看流星鏈接:https://www.jianshu.com/p/50fffbf21b39

      1、前言

      本篇文章主要介紹的是Java(Javaee和Android開發(fā)都會涉及)中的線程池。線程池不僅是Java多線程編程的重要基礎,而且也是Android面試和Javaee面試中,面試官心血來潮突然向你發(fā)難的一道面試題(可能他自己也說不清楚道不明白線程池的概念和應用場景,但他們就是想見你一臉尷尬的表情以此作為壓低你工資的籌碼)。說到線程池之前,我們首先必須理清楚 線程、并行和并發(fā)、多線程的基本概念。

      線程池的概念大致理清楚之后,我們在分析OkHttp這一經典網絡框架內部的線程池是如何實現的,通過分析源碼進一步加深線程池概念。文章一如既往原來的風格,篇幅較長但力爭寫得詳細明白。筆者從開始寫到發(fā)布花了自己近一周的業(yè)余時間。(現已經對部分內容已進行了刪減,初版達到了萬字)個人希望看到這篇文章的開發(fā)者可以耐心、仔細、慢慢的看完,分段閱讀也是較好的選擇。

      所謂:九層之臺、起于壘土 。

      另外對本文有任何意見或者覺得文章內容有所不足請在評論區(qū)直接提出issue,謝謝。

      線程:

      說起線程,大家可能都不陌生。線程,是程序執(zhí)行的最小單元。一個標準的線程由線程ID,當前指令指針,寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統(tǒng)獨立調度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。

      一個線程可以創(chuàng)建和撤消另一個線程,同一進程中的多個線程之間可以并發(fā)執(zhí)行。以上概念來自于百度百科。對于開發(fā)者來說,線程就是幫我們干實事的伙伴。在Java中,對于線程的基本操作,我們知道在代碼中有以下三種寫法:

      (1)自定義一個類,去繼承Thread類,重寫run方法

      (2)自定義一個類,去實現Runnable接口,重寫run方法

      (3)自定義一個類,實現Callable接口,重寫call方法。

      關于這個Callable,要多提一嘴,首先,Callable規(guī)定的方法是call(),而Runnable規(guī)定的方法是run();

      其次,Callable的任務執(zhí)行后可返回值,而Runnable的任務是不能返回值的;然后,call()方法可拋出異常,而run()方法是不能拋出異常的;最后,運行Callable任務可拿到一個Future對象。

      必須要理清的Java線程池

      因為線程的基本概念和使用大家基本上都很熟悉,所以這里就點到為止。

      關于線程,就不得不提及另外一個經常容易被混淆的概念,那就是并行和并發(fā)。

      關于并行和并發(fā),我在網上找了一段關于并行和并發(fā)的英文資料:

      Concurrency is when two tasks can start, run, and complete in overlapping time periods. Parallelism is when tasks literally run at the same time, eg. on a multi-core processor.Concurrency is the composition of independently executing processes, while parallelism is the simultaneous execution of (possibly related) computations.Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.An application can be concurrent – but not parallel, which means that it processes more than one task at the same time, but no two tasks are executing at same time instant.An application can be parallel – but not concurrent, which means that it processes multiple sub-tasks of a task in multi-core CPU at same time.An application can be neither parallel – nor concurrent, which means that it processes all tasks one at a time, sequentially.An application can be both parallel – and concurrent, which means that it processes multiple tasks concurrently in multi-core CPU at same time.

      翻譯過來就是:

      并發(fā)是兩個任務可以在重疊的時間段內啟動,運行和完成。并行是任務在同一時間運行,例如,在多核處理器上。并發(fā)是獨立執(zhí)行過程的組合,而并行是同時執(zhí)行(可能相關的)計算。并發(fā)是一次處理很多事情,并行是同時做很多事情。應用程序可以是并發(fā)的,但不是并行的,這意味著它可以同時處理多個任務,但是沒有兩個任務在同一時刻執(zhí)行。應用程序可以是并行的,但不是并發(fā)的,這意味著它同時處理多核CPU中的任務的多個子任務。

      一個應用程序可以即不是并行的,也不是并發(fā)的,這意味著它一次一個地處理所有任務。

      應用程序可以即是并行的也是并發(fā)的,這意味著它同時在多核CPU中同時處理多個任務。

      看完了這些,可能還是懵懵懂懂。為了徹底理清并行和并發(fā)的概念,我找了兩幅不錯的資料圖來幫助我們鞏固理解區(qū)分并行和并發(fā)。

      首先是并發(fā):

      必須要理清的Java線程池

      并發(fā)( Concurrency )

      Concurrency,是并發(fā)的意思。并發(fā)的實質是一個物理CPU(也可以多個物理CPU) 在若干道程序(或線程)之間多路復用,并發(fā)性是對有限物理資源強制行使多用戶共享以提高效率。

      從微觀角度來講:所有的并發(fā)處理都有排隊等候,喚醒,執(zhí)行等這樣的步驟,在微觀上他們都是序列被處理的,如果是同一時刻到達的請求(或線程)也會根據優(yōu)先級的不同,而先后進入隊列排隊等候執(zhí)行。

      從宏觀角度來講:多個幾乎同時到達的請求(或線程)在宏觀上看就像是同時在被處理。

      通俗點講,并發(fā)就是只有一個CPU資源,程序(或線程)之間要競爭得到執(zhí)行機會。圖中的第一個階段,在A執(zhí)行的過程中B,C不會執(zhí)行,因為這段時間內這個CPU資源被A競爭到了,同理,第二個階段只有B在執(zhí)行,第三個階段只有C在執(zhí)行。其實,并發(fā)過程中,A,B,C并不是同時在進行的(微觀角度)。但又是同時進行的(宏觀角度)。

      簡單說完了并發(fā),我們繼續(xù)分析并行:

      必須要理清的Java線程池

      并行(Parallelism)

      Parallelism,翻譯過來即并行,指兩個或兩個以上事件(或線程)在同一時刻發(fā)生,是真正意義上的不同事件或線程在同一時刻,在不同CPU資源上(多核),同時執(zhí)行。

      并行,不存在像并發(fā)那樣競爭,等待的概念。

      圖中,A,B,C都在同時運行(微觀,宏觀)。

      關于并行和并發(fā)的基本概念大概介紹到這里,如果還覺得不是很好理解,請自行谷歌或者百度查閱資料慢慢消化。

      上面簡單回顧了下線程、并行和并發(fā)的基本概念,下面我們在說說多線程。

      首先,什么是多線程?

      我們知道,一個任務就是一個線程 ,但實際上,一個應用程序為了同時執(zhí)行多個任務提供運行效率,一般會涉及到一個線程以上的數量。如果,一個應用程序有一個以上的線程,我們把這種情況就稱之為多線程。

      本質來說,多線程是為了使得多個線程完成多項任務,以提高系統(tǒng)的效率。目前為止我們使用多線程應用程序的目的是盡可能多地使用計算機處理器資源(本質是為了讓效率最大化)。

      所以,看起來我們僅需要為每個獨立的任務分配一個不同的線程,并讓處理器確定在任何時間它總會處理其中的某一個任務。但是,這樣就會出現一些問題,對小系統(tǒng)來說這樣做很好。但是當系統(tǒng)越來越復雜時,線程的數量也會越來越多,操作系統(tǒng)將會花費更多時間去理清線程之間的關系。為了讓我們的程序具備可擴展性,我們將不得不對線程進行一些有效的控制。

      針對這種情況,開發(fā)者通過使用線程池就可以有效規(guī)避上述風險。

      什么是線程池?

      線程池是指在初始化一個多線程應用程序過程中創(chuàng)建一個線程集合,然后在需要執(zhí)行新的任務時重用這些線程而不是新建一個線程(提高線程復用,減少性能開銷)。線程池中線程的數量通常完全取決于可用內存數量和應用程序的需求。然而,增加可用線程數量是可能的。線程池中的每個線程都有被分配一個任務,一旦任務已經完成了,線程回到池子中然后等待下一次分配任務。

      為什么要用線程池:

      基于以下幾個原因在多線程應用程序中使用線程池是必須的:

      1. 線程池改進了一個應用程序的響應時間。由于線程池中的線程已經準備好且等待被分配任務,應用程序可以直接拿來使用而不用新建一個線程。

      2. 線程池節(jié)省了CLR 為每個短生存周期任務創(chuàng)建一個完整的線程的開銷并可以在任務完成后回收資源。

      3. 線程池根據當前在系統(tǒng)中運行的進程來優(yōu)化線程時間片。

      4. 線程池允許我們開啟多個任務而不用為每個線程設置屬性。

      5. 線程池允許我們?yōu)檎趫?zhí)行的任務的程序參數傳遞一個包含狀態(tài)信息的對象引用。

      6. 線程池可以用來解決處理一個特定請求最大線程數量限制問題。

      本質上來講,我們使用線程池主要就是為了減少了創(chuàng)建和銷毀線程的次數,每個工作線程都可以被重復利用,可執(zhí)行多個任務;節(jié)約應用內存(線程開的越多,消耗的內存也就越大,最后死機)

      線程池的作用:

      線程池作用就是限制系統(tǒng)中執(zhí)行線程的數量。根據系統(tǒng)的環(huán)境情況,可以自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統(tǒng)資源,多了造成系統(tǒng)擁擠效率不高。用線程池控制線程數量,其他線程排隊等候。

      一個任務執(zhí)行完畢,再從隊列的中取最前面的任務開始執(zhí)行。若隊列中沒有等待進程,線程池的這一資源處于等待。當一個新任務需要運行時,如果線程池中有等待的工作線程,就可以開始運行了;否則進入等待隊列。

      說完了線程池的概念和作用,我們再看看代碼中的線程池:

      在Java中,線程池的代碼起源之Executor(翻譯過來就是執(zhí)行者)注意:這個類是一個接口。

      必須要理清的Java線程池

      但是嚴格意義上講Executor并不是一個線程池(如圖其源碼就一個 execute 方法),所以Executor僅只是一個執(zhí)行線程的工具。那么,線程池的真正面紗是什么?利用AS的類繼承關系發(fā)現,Executor有一個 ExecutorService 子接口。

      必須要理清的Java線程池

      實際上,一般說線程池接口,基本上說的是這個 ExecutorService。ExecutorService源碼里面有各種API(比如說執(zhí)行 excute ( xxx ),比如關閉 isShutdown ( ))幫助我們去使用。ExecutorService接口的默認實現類為ThreadPoolExecutor(翻譯過來就是線程池執(zhí)行者)。既然是默認實現類我們就可以根據應用場景去私人訂制了。

      必須要理清的Java線程池

      必須要理清的Java線程池

      既然找到了突破口,那我們集中火力先去了解下ThreadPoolExecutor

      必須要理清的Java線程池

      ThreadPoolExecutor構造參數

      首先從ThreadPoolExecutor 的構造參數開始分析,通過代碼截圖得知,ThreadPoolExecutor的構造方法有以下4種:

      必須要理清的Java線程池

      下面就構造方法里面的參數逐一分析說明:

      1:int corePoolSize (core:核心的) = > 該線程池中核心線程數最大值

      什么是核心線程:線程池新建線程的時候,如果當前線程總數小于 corePoolSize ,則新建的是核心線程;如果超過corePoolSize,則新建的是非核心線程。

      核心線程默認情況下會一直存活在線程池中,即使這個核心線程啥也不干(閑置狀態(tài))。

      如果指定ThreadPoolExecutor的 allowCoreThreadTimeOut 這個屬性為true,那么核心線程如果不干活(閑置狀態(tài))的話,超過一定時間( keepAliveTime),就會被銷毀掉

      2:int maximumPoolSize = > 該線程池中線程總數的最大值

      線程總數計算公式 = 核心線程數 + 非核心線程數。

      3:long keepAliveTime = > 該線程池中非核心線程閑置超時時長

      注意:一個非核心線程,如果不干活(閑置狀態(tài))的時長,超過這個參數所設定的時長,就會被銷毀掉。但是,如果設置了 allowCoreThreadTimeOut = true,則會作用于核心線程。

      4:TimeUnit unit = > (時間單位)

      首先,TimeUnit是一個枚舉類型,翻譯過來就是時間單位,我們最常用的時間單位包括:

      MILLISECONDS : 1毫秒 、SECONDS : 秒、MINUTES : 分、HOURS : 小時、DAYS : 天

      5:BlockingQueue<Runnable> workQueue = >( Blocking:阻塞的,queue:隊列)

      該線程池中的任務隊列:維護著等待執(zhí)行的Runnable對象。當所有的核心線程都在干活時,新添加的任務會被添加到這個隊列中等待處理,如果隊列滿了,則新建非核心線程執(zhí)行任務

      必須要理清的Java線程池

      其中,BlockingQueue中具體的API介紹:

      offer(E e): 將給定的元素設置到隊列中,如果設置成功返回true, 否則返回false. e的值不能為空,否則拋出空指針異常。

      offer(E e, long timeout, TimeUnit unit): 將給定元素在給定的時間內設置到隊列中,如果設置成功返回true, 否則返回false.

      add(E e): 將給定元素設置到隊列中,如果設置成功返回true, 否則拋出異常。如果是往限定了長度的隊列中設置值,推薦使用offer()方法。

      put(E e): 將元素設置到隊列中,如果隊列中沒有多余的空間,該方法會一直阻塞,直到隊列中有多余的空間。

      take(): 從隊列中獲取值,如果隊列中沒有值,線程會一直阻塞,直到隊列中有值,并且該方法取得了該值。

      poll(long timeout, TimeUnit unit): 在給定的時間里,從隊列中獲取值,如果沒有取到會拋出異常。

      remainingCapacity():獲取隊列中剩余的空間。

      remove(Object o): 從隊列中移除指定的值。

      contains(Object o): 判斷隊列中是否擁有該值。

      drainTo(Collection c): 將隊列中值,全部移除,并發(fā)設置到給定的集合中。

      說完了BlockingQueue常用的API,在說說其常用的workQueue類型:

      一般來說,workQueue有以下四種隊列類型:

      SynchronousQueue:(同步隊列)這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它(名字定義為 同步隊列)。但有一種情況,假設所有線程都在工作怎么辦?

      這種情況下,SynchronousQueue就會新建一個線程來處理這個任務。所以為了保證不出現(線程數達到了maximumPoolSize而不能新建線程)的錯誤,使用這個類型隊列的時候,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大,去規(guī)避這個使用風險。

      LinkedBlockingQueue(鏈表阻塞隊列):這個隊列接收到任務的時候,如果當前線程數小于核心線程數,則新建線程(核心線程)處理任務;如果當前線程數等于核心線程數,則進入隊列等待。由于這個隊列沒有最大值限制,即所有超過核心線程數的任務都將被添加到隊列中,這也就導致了maximumPoolSize的設定失效,因為總線程數永遠不會超過corePoolSize

      ArrayBlockingQueue(數組阻塞隊列):可以限定隊列的長度(既然是數組,那么就限定了大?。邮盏饺蝿盏臅r候,如果沒有達到corePoolSize的值,則新建線程(核心線程)執(zhí)行任務,如果達到了,則入隊等候,如果隊列已滿,則新建線程(非核心線程)執(zhí)行任務,又如果總線程數到了maximumPoolSize,并且隊列也滿了,則發(fā)生錯誤

      DelayQueue(延遲隊列):隊列內元素必須實現Delayed接口,這就意味著你傳進去的任務必須先實現Delayed接口。這個隊列接收到任務時,首先先入隊,只有達到了指定的延時時間,才會執(zhí)行任務

      說完了BlockingQueue,繼續(xù)回到ThreadPoolExecutor的構造參數上面

      6:ThreadFactory threadFactory = > 創(chuàng)建線程的方式,這是一個接口,new它的時候需要實現他的Thread newThread(Runnable r)方法

      7:RejectedExecutionHandler handler = > 這個主要是用來拋異常的

      當線程無法執(zhí)行新任務時(一般是由于線程池中的線程數量已經達到最大數或者線程池關閉導致的),默認情況下,當線程池無法處理新線程時,會拋出一個RejectedExecutionException。

      構造參數基本上就介紹完畢了。

      (如果看累了就先休息下。。。內容的確較多。。。)

      花了這么大篇幅去介紹ThreadPoolExecutor這個類的構造函數,可能你會覺得好累好空虛,好吧,其實我們的付出都是為打通線程池最后的壁壘做的必要準備,因為 千里之行、始于足下 ,我們始終要堅信 倘想達到最高處,就要從低處開始 。

      說完了這么多,我們知道了實例化一個線程池,只需要在構造參數里面去添加自己設置的屬性值(設置正確即可使用,設置錯誤即拋異常),這樣問題就來了:

      一個任務,它是如何進入線程池去執(zhí)行任務?

      ThreadPoolExecutor這個類,里面有一個API,在上面也隨口提到過,有一個執(zhí)行的方法,先上圖

      必須要理清的Java線程池

      首先我們初始化一個線程池后,即可調用 execute這個方法,里面?zhèn)魅隦unnable即可向線程池添加任務。

      問題又來了,既然線程池新添加了任務,那么線程池是如何處理這些批量任務?

      1:如果線程數量未達到corePoolSize,則新建一個線程(核心線程)執(zhí)行任務

      2:如果線程數量達到了corePools,則將任務移入隊列等待

      3:如果隊列已滿,新建線程(非核心線程)執(zhí)行任務

      4:如果隊列已滿,總線程數又達到了maximumPoolSize,就會由RejectedExecutionHandler拋出異常

      但是,實際上,Java已經為我們提供了四種線程池!

      好吧,在Java中,Executors這個類已經為我們提供了常用的四種線程池,分別為:

      A:newFixedThreadPool 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數,超出的線程會在隊列中等待。

      必須要理清的Java線程池

      源碼注釋翻譯:

      創(chuàng)建一個線程池,使用固定數量的線程在共享的無界隊列中操作。

      在任何時候,有最多 nThreads(就是我們傳入參數的數量)的線程將處理任務。

      如果所有線程都處于活動狀態(tài)時,提交額外的任務,他們會在隊列中等待,直到有一個線程可用。

      如果在執(zhí)行過程中出現故障,任何線程都會終止。如果需要執(zhí)行后續(xù)任務,新的任務將取代它的位置。線程池中的線程會一直存在,直到它顯式為止(調用shutdown)

      nThreads 就是傳入線程池的數量 ,當nThreads <= 0 就會拋異常IllegalArgumentException

      B:newCachedThreadPool創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

      必須要理清的Java線程池

      源碼注釋翻譯:

      創(chuàng)建一個線程池,根據需要創(chuàng)建新線程,但是將重寫之前線程池的構造。

      這個線程池通常會提高性能去執(zhí)行許多短期異步任務的程序。

      如果有可用線程,當線程池調用execute, 將重用之前的構造函數。

      如果沒有現有的線程可用,那么就創(chuàng)建新的線程并添加到池中。

      線程沒有使用60秒的時間被終止并從線程池里移除緩存。

      因此,一個閑置時間足夠長的線程池不消耗任何資源。

      注意,線程池有類似的屬性,但有一些不同的細節(jié)(例如,超時參數)可以使用@link ThreadPoolExecutor構造函數創(chuàng)建。

      C:newScheduledThreadPool 創(chuàng)建一個定長任務線程池,支持定時及周期性任務執(zhí)行。

      必須要理清的Java線程池

      源碼注釋翻譯:

      創(chuàng)建一個線程池,它可以安排在 a 之后運行的命令給定延遲,或定期執(zhí)行。

      corePoolSize (這個參數) 是指在池中保留的線程數,即使它們是空閑的。這個函數最終會返回一個新創(chuàng)建的調度線程池

      如果 corePoolSize < 0 ,則會拋出 IllegalArgumentException

      Ps:這個還支持多傳入一個ThreadFactory

      D:newSingleThreadExecutor 創(chuàng)建一個單線程的線程池,它只會用唯一的工作線程來執(zhí)行任務,保證所有任務按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。

      必須要理清的Java線程池

      源碼注釋翻譯:

      創(chuàng)建一個線程執(zhí)行器,它使用單個運行中的線程操作在一個無界隊列中。

      請注意,如果這個單獨的線程終止是因為在執(zhí)行前異常或者終止,若需要執(zhí)行后續(xù)的任務,那么就需要一個新的去替代它。

      任務被保證按順序的執(zhí)行,并且在任何給定的時間內不超過一個任務將是活動的。

      不像其他等價 newFixedThreadPool(1) 這個返回的線程池對象是保證不運行重新配置以使用額外的線程。

      最終返回的是一個重新創(chuàng)建的單線程去執(zhí)行。

      總結:

      Java為我們提供的四種線程池基本上就介紹完畢了。可以看到,這四種每一個具體的線程池都是 跟 ThreadPoolExecutor 配置有關的。因此,前面花大篇幅介紹ThreadPoolExecutor的構造參數在這里就起到了作用,整體來說,線程池的基本概念就結束了。

      下面上一份偽代碼加深理解

      必須要理清的Java線程池

      好了,說完了這么多,我們在回歸到Android上面,在Android里面,線程池的應用場景是什么?

      這個問題面試也經常問(面試常規(guī)套路就是,先讓你回答Java的某一個知識概念,在讓你談這一知識概念在Android的應用場景)常見的有異步任務棧,(AsyncTask)其內部就是使用到了線程池,關于異步任務棧的線程池這個網上資料就很多了這里就不多說了,我們今天就用上面的概念簡單分析討論下OkHttp這一經典網絡框架的內置線程池。

      Okhttp的常用寫法如下:

      必須要理清的Java線程池

      配置好請求體,url之后,我們會使用 OkHttpClient這個對象首先去調用 newCall,也就是上圖紅色的框框,點進去這個newCall

      必須要理清的Java線程池

      這個方法返回了一個RealCall,翻譯過來就是 (真正的請求),點進去看下RealCall

      必須要理清的Java線程池

      所以,本質上來講 OkHttpClient.newCall(request).enqueue(), 其實就是調用 RealCall 類里面的 enqueue 方法。如下圖

      必須要理清的Java線程池

      黃色框框里面可以看到,這個方法最終調用的是 client.dispatcher().enqueue,這個方法內部引用了AsyncCall這個對象,那這AsyncCall又是什么?

      點擊AsyncCall后發(fā)現,原來,AsyncCall 是 RealCall 的一個內部類(位于:RealCall 源碼 124行)

      必須要理清的Java線程池

      可能你會問, NamedRunnable 這個類又是什么?點進 NamedRunnable后發(fā)現,這個類的本質其實就是一個 Runnable

      必須要理清的Java線程池

      好了,言歸正傳我們再回到client.dispatcher().enqueue這個方法,點擊 enqueue ,進入到了Dispatcher 這個類里面的enqueue方法(Dispatcher 翻譯過來就是:調度員、分配器)

      必須要理清的Java線程池

      是不是驚奇的看見了executorService().execute(call),是不是感覺有點類似線程池的寫法(關于 ExecutorService 這個是文章前面說到的;還有,AsyncCall本質就是一個Runnable對象,線程池里面的execte方法里需要的就是一個Runnable對象),我們繼續(xù)點擊 executorService()這個方法內,看看其廬山真面目

      必須要理清的Java線程池

      好家伙,是不是看到了熟悉的線程池。如果只是為了應付面試讓自己有個概念,看到這里基本上就可以了。因為接下來就開始深挖OkHttp內部線程池細節(jié),內容有點繞,如果愿意深挖的就請繼續(xù)耐心看完下面的內容。

      既然,Okhttp在這里幫我們創(chuàng)建了一個線程池。那么這個線程池是怎么處理的?通過源碼得知,構造參數里面有一個SynchronousQueue (同步),這個在上面的構造參數里面也分析過,這個隊列接收到任務的時候,會直接提交給線程處理,而且也提到使用這個隊列的話,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大去規(guī)避使用風險,在這里,OkHttp的源碼也使用到了無限大。

      我們知道,okhttp發(fā)起請求的步驟真正的執(zhí)行是在RealCall這個類里面,里面的enqueue方法調用了

      client.dispatcher().enqueue(new AsyncCall(responseCallback)) ;

      必須要理清的Java線程池

      因此我們在看回去 Dispatcher這個類里面的enqueue方法:

      必須要理清的Java線程池

      這三個集合簡單介紹下:

      private final Deque readyAsyncCalls = new ArrayDeque<>() ; // 正在準備中的異步請求隊列

      private final Deque running AsyncCalls = new ArrayDeque<>(); //運行中的異步請求

      private final Deque runningSyncCalls = new ArrayDeque<>(); // 同步請求

      拓展:Deque 這個是什么?簡單理解,這個Deque它是Queue的子接口,我們知道Queue是一種隊列形式,而Deque則是雙向隊列,它支持從兩個端點方向檢索和插入元素(也就是頭部和尾部加入元素)

      我們繼續(xù)看下enqueue方法:

      必須要理清的Java線程池

      在這里有個判斷,如果 (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) 如果這個條件不成立的時候,要把任務放在 readyAsyncCalls 里。

      問題來了,那么什么時候去取這里的任務來執(zhí)行呢?

      為了解決這個問題,我們需要在回到AsyncCall這個類一探究竟,

      必須要理清的Java線程池

      必須要理清的Java線程池

      AsyncCall - 1圖中,截圖了三種不同顏色的矩形,是不是感覺紅色和綠色矩形對應的兩個方法很熟悉,

      responseCallback.onFailure ()實際上就是失敗的回調

      responseCallback.onResponse ()實際上就是成功的回調,將藍色矩形的Response對象回調出去。

      那么藍色矩形里面的代碼是什么意思?getResponseWithInterceptorChain()這個方法具體是做什么的?繼續(xù)點進源碼看看

      必須要理清的Java線程池

      哦,這個方法就是遍歷外部定義的攔截器 然后添加OkHttp內部的攔截器去發(fā)起真正的請求。攔截器的本質就是攔截請求體,攔截響應體,在攔截的過程中添加信息和修改信息,比如添加請求頭等等。由于這個方法涉及到攔截器的源碼實在是太多了,想要細看的朋友可以在網上自行查閱資料。所以這里就點到為止。

      可以看到,這個方法最終返回了一個Response對象。

      回到上面那副AsyncCall - 1圖,其中紅色和綠色矩形的源碼,對應的就是下圖OkHttp常規(guī)寫法,通過接口回調將Response結果,告知調用者,藍色矩形的response的body,就是我們請求成功之后獲取的響應體。

      必須要理清的Java線程池

      我們看到AsyncCall - 2圖中, finally 里面有個結束的處理,也就是 client.dispatcher().finished(this) 這個方法,嘗試點進去看

      必須要理清的Java線程池

      進入到finished方法之后,發(fā)現回到了Dispatch這個類里面。這個finished方法 里面貌似能看的,就 promoteCalls 方法和runningCallsCount方法,我們首先看看runningCallsCount方法

      必須要理清的Java線程池

      發(fā)現這個方法其實返回的就是長度(返回值就是int嘛),這不是我們想要的結果。所以繼續(xù)回到上一步,點擊promoteCalls 方法點進去看看

      必須要理清的Java線程池

      哦,是不是終于看到了 executorService().execute(call) ,將任務添加到線程池里面的具體代碼。

      這個方法的主要功能是,首先遍歷 readyAsyncCalls,把任務取出來;把取出來的任務加入 到runningAsyncCalls;最后, 把任務放入線程池。原來把任務添加到線程池是在Dispatcher這里進行的。(這個類內部不僅創(chuàng)建了線程池,也將任務添加了進來)

      總體來說,當請求任務數大于 maxRequests 并且相同 host 最大請求數大于 maxRequestsPerHost,就會把請求任務放在 readyAsyncCalls 隊列里;當線程池里執(zhí)行任務的 runnable 執(zhí)行完任務在最后會檢查 readyAsyncCalls 里有沒有任務,如果有任務并且是同一個 host 就放入到線程池中執(zhí)行。因此通過這個方法不斷地從 readyAsyncCalls 隊列里取出任務,對線程池里的線程進行復用。

      關于Okhttp內置線程池和部分源碼的分析就寫到這里了,希望對開發(fā)者有一點點小幫助吧。如果面試官在問到線程池在Android的實際應用的時候,我們除了說異步任務棧(面試官可能也準備好了異步任務棧讓你入坑跟你討論其內部細節(jié)),可以直接跟他談OkHttp框架內部如何創(chuàng)建線程池、如何將任務添加到線程池等具體的內部實現流程。

      如果這篇文章對你有幫助,希望開發(fā)者朋友可以留下寶貴的star,謝謝。

      參考資料:https://www.jianshu.com/p/210eab345423參考資料:https://www.cnblogs.com/miller-zou/p/6978533.html

        本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多