在分享線程的監控以前,咱們要來先講講線程的基礎知識,通常來講只要咱們基礎牢固,在寫代碼的時候大部分狀況下不容易犯錯。但在 Android 團隊人數達到幾十人甚至上百人的時候,咱們就沒法確保全部的同窗都能循序漸進的寫好代碼了,因此咱們仍是要有監控,但光有監控是不行的還須要有理論基礎,這樣的話出現了問題才能分析解決。有不少同窗認爲線程有什麼好了解的,無非就是 synchronized 、volatile、newThread,啓動線程。不少同窗可能連線程池和 lock 鎖都沒接觸過,說句實話早先年我也跟你們同樣,由於項目中用不上只能本身去看源碼學原理。緩存
在過去單 CPU 時代,單任務在一個時間點只能執行單一程序。以後發展到多任務階段,計算機能在同一時間點並行執行多任務或多進程。雖然並非真正意義上的「同一時間點」,而是多個任務或進程共享一個 CPU,並交由操做系統來完成多任務間對 CPU 的運行切換,以使得每一個任務都有機會得到必定的時間片運行。再後來發展到多線程技術,使得在一個程序內部能擁有多個線程並行執行。一個線程的執行能夠被認爲是一個 CPU 在執行該程序。當一個程序運行在多線程下,就好像有多個 CPU 在同時執行該程序。多線程比多任務更加有挑戰。多線程是在同一個程序內部並行執行,所以會對相同的內存空間進行併發讀寫操做。這多是在單線程程序中歷來不會遇到的問題。其中的一些錯誤也未必會在單 CPU 機器上出現,由於兩個線程歷來不會獲得真正的並行執行。然而,更現代的計算機伴隨着多核 CPU 的出現,也就意味着 不一樣的線程能被不一樣的 CPU 核獲得真正意義的並行執行。因此,在多線程、多任務狀況下,線程上下文切換是必須的,然而對於 CPU 架構設計中的概念,應先熟悉瞭解,這樣會有助於理解線程上下文切換原理。安全
多進程多線程在運行的過程當中都離不開一個概念,那就是調度。JVM 虛擬機雖是跨平臺可是並未接管線程調度,調度仍是由操做系統自己來決定,咱們在下次看線程建立底層源碼便會知道。調度會涉及到一個上下文切換的概念,多任務多線程的本質其實就是 CPU 時間片的輪轉,在多任務處理系統中,CPU 須要處理全部程序的操做,當用戶來回切換它們時,須要記錄這些程序執行到哪裏。上下文切換就是這樣一個過程,容許 CPU 記錄並恢復各類正在運行程序的狀態,使它可以完成切換操做。簡單一點說就是指 CPU 從一個進程或線程切換到另外一個進程或線程。markdown
在上下文切換過程當中,CPU 會中止處理當前運行的程序,並保存當前程序運行的具體位置以便以後繼續運行。從這個角度來看,上下文切換有點像咱們同時閱讀幾本書,在來回切換書本的同時咱們須要記住每本書當前讀到的頁碼。在程序中,上下文切換過程當中的「頁碼」信息是保存在進程控制塊(PCB, process control block)中的。PCB 還常常被稱做「切換楨」(switchframe)。「頁碼」信息會一直保存到 CPU 的內存中,直到他們被再次使用。PCB 一般是系統內存佔用區中的一個連續存區,它存放着操做系統用於描述進程狀況及控制進程運行所需的所有信息,它使一個在多道程序環境下不能獨立運行的程序成爲一個能獨立運行的基本單位或一個能與其餘進程併發執行的進程。多線程
對於一個正在執行的進程包括 程序計數器、寄存器、變量的當前值等 ,而這些數據都是 保存在 CPU 的寄存器中的,且這些寄存器只能是正在使用 CPU 的進程才能享用,在進程切換時,首先得保存上一個進程的這些數據(便於下次得到 CPU 的使用權時從上次的中斷處開始繼續順序執行,而不是返回到進程開始,不然每次進程從新得到 CPU 時所處理的任務都是上一次的重複,可能永遠也到不了進程的結束出,由於一個進程幾乎不可能執行完全部任務後才釋放 CPU ),而後將本次得到 CPU 的進程的這些數據裝入 CPU 的寄存器從上次斷點處繼續執行剩下的任務。架構
上下文切換會帶來直接和間接兩種因素影響程序性能的消耗。**直接消耗:**指的是 CPU 寄存器須要保存和加載, 系統調度器的代碼須要執行, TLB 實例須要從新加載, CPU 的 pipeline 須要刷掉;**間接消耗:**指的是多核的 cache 之間得共享數據, 間接消耗對於程序的影響要看線程工做區操做數據的大小;所以咱們在多線程操做時應該要考慮兩個問題:第一個是儘可能減小上下文的切換次數,第二個是儘可能提升 CPU 的使用率。併發
在介紹 Java 內存模型以前,先來看一下到底什麼是計算機內存模型,而後再來看 Java 內存模型在計算機內存模型的基礎上作了哪些事情。先看一下爲何要有內存模型。oop
咱們應該都知道,計算機在執行程序的時候,每條指令都是在 CPU 中執行的,而執行的時候,又免不了要和數據打交道。而計算機上面的數據,是存放在主存當中的,也就是計算機的物理內存啦。剛開始,還相安無事的,可是隨着 CPU 技術的發展,CPU 的執行速度愈來愈快。而因爲內存的技術並無太大的變化,因此從內存中讀取和寫入數據的過程和 CPU 的執行速度比起來差距就會愈來愈大,這就致使 CPU 每次操做內存都要耗費不少等待時間。因此,人們想出來了一個好的辦法,就是在 CPU 和內存之間增長高速緩存。緩存的概念你們都知道,就是保存一份數據拷貝。它的特色是速度快,內存小,而且昂貴。那麼,程序的執行過程就變成了:當程序在運行過程當中,會將運算須要的數據從主存複製一份到 CPU 的高速緩存當中,那麼 CPU 進行計算時就能夠直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束以後,再將高速緩存中的數據刷新到主存當中。隨着 CPU 能力的不斷提高,一層緩存就慢慢的沒法知足要求了,就逐漸的衍生出多級緩存。按照數據讀取順序和與CPU結合的緊密程度,CPU 緩存能夠分爲一級緩存(L1),二級緩存(L3),部分高端 CPU 還具備三級緩存(L3),每一級緩存中所儲存的所有數據都是下一級緩存的一部分。這三種緩存的 技術難度和制形成本是相對遞減的,因此其容量也是相對遞增的。那麼,在有了多級緩存以後,程序的執行就變成了:當 CPU 要讀取一個數據時,首先從一級緩存中查找,若是沒有找到再從二級緩存中查找,若是仍是沒有就從三級緩存或內存中查找。單核 CPU 只含有一套L1,L2,L3緩存。若是 CPU 含有多個核心,即多核 CPU,則每一個核心都含有一套L1(甚至和L2)緩存,而共享L3(或者和L2)緩存。性能
從上面的分析來看,這樣就會致使一個問題,那就是多線程 CUP 緩存一致性的問題,也就是你們經常說的**原子性問題,可見性問題和有序性問題等等。其本質其實就是 CPU 緩存優化後所帶來的後遺症。**出現問題就得解決問題,按照咱們平時普通的思路就是回退版本,廢除掉處理器和處理器的優化技術、廢除CPU緩存,讓CPU直接和主存交互,這確定是不行的。所以內存模型就誕生了,內存模型就是用來解決 CPU 緩存優化後所帶來的後遺症。Java 的內存模型以下:優化
Java 後臺工程師常常會碰到一些問題像 CPU 飆高、Load 高、響應很慢等等,做爲 Android 工程師因爲不多會涉及到併發請求的處理,所以咱們不多會刨根問底的去深究線程這一塊。雖然遇到的問題可能會千奇百怪可是問題的本質是不會變的,這也是爲何我一再強調你們要把基礎打牢,要多花些時間在 Linux 內核和系統源碼上面。能夠這麼說,在 Android 場景下咱們遇到的線程問題,只要從 Linux 內核和 JVM 的內存模型這兩個方向去分析便可。spa
線程池參數有很是多:核心線程數、最大線程數,隊列等等。在實際的過程當中怎麼用呢?其實無非就是前面提到的兩點:
直接看上去這兩點像是衝突了,但在實際的場景中是不衝突的,好比咱們在系統架構時分析過 OkHttp 的源碼,咱們不妨來看下它內部使用的線程池:
public synchronized ExecutorService executorService() {
if (executorService == null) {
// 核心線程數是 0 ,最大線程數是 Integer.MAX_VALUE
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
複製代碼
synchronized 的底層實現原理之前分析過這裏就再也不作過多的介紹,lock 的源碼這個須要你們本身去看看,網上有不少的文章你們也能夠去輔助瞭解一下。這裏咱們只提一個你們可能沒留意的一個區別,synchronized 若是競爭不到鎖會致使上下文切換,這也是爲何若是沒有多線程安全的狀況下,就不要隨意加鎖的緣由。可是 lock 採用的通常是 Unsafe 底層的原理就是等待主線上的數據刷新。看上去好像 Lock 更好些,能夠減小上下文的切換次數,其實也不徹底正確,具體場景須要具體對待。
視頻連接: pan.baidu.com/s/1pZA2udae… 視頻密碼: 87uh