多線程編程或者說範圍更大的併發編程是一種很是複雜且容易出錯的編程方式,可是咱們爲何還要冒着風險艱辛地學習各類多線程編程技術、解決各類併發問題呢?java
由於併發是整個分佈式集羣的基礎,經過分佈式集羣不只能夠大大下降同等負載能力的價格,還能使總體可擴展到的負載能力上限大大提高。低廉的服務成本使互聯網行業的創意井噴,任何一我的都有能力建立並維持一個服務於成百上千甚至數萬人的應用服務;而極高的服務能力上限讓無數業務的線上化成爲了可能,大大拓寬了互聯網技術與業務的邊界。程序員
在這個範圍廣大的併發技術領域當中多線程編程能夠說是基礎和核心,大多數抽象併發問題的構思與解決都是基於多線程模型來進行的。並且這些併發問題的本質都是相同的,不論是線程併發、進程併發仍是服務器級別的併發都具備相似的特色、面臨類似的問題,多線程編程正是咱們切入這個領域、學習併發問題解決方案的最好途徑。因此,在如今的計算機行業中,多線程編程不只是Java程序員技術面試、進階提升的重要知識領域,並且也是後端程序員敲開分佈式系統實現大門的入場券。若是不能理解併發程序的特色與問題,那麼就難以勝任分佈式系統開發的工做。面試
這篇文章是一系列文章的總集篇,因此不須要讀者有多線程相關的基礎。文中會按照合理的順序按部就班地介紹Java多線程編程的方方面面,由淺入深地講解多線程編程的概念、使用、原理與實現。在每一部分都有對相關主題的簡單介紹,再搭配上深刻講解的文章連接,建議還不瞭解相關主題的讀者能夠深刻閱讀連接中的文章來進行了解。但若是文章中間的一些內容你們已經很是熟悉了,那麼能夠略讀而過,不用理會連接中的文章,徹底能夠把這部份內容當作複習提綱來看。數據庫
接下來,咱們會在這篇文章中系統地瞭解Java多線程編程知識體系,從最基礎的基本概念、線程的使用開始講起,一路覆蓋多線程的正確性與運行效率相關議題,幫助你們從0到入門再到熟練掌握各類多線程編程技巧。在這以後,文章會漸趨複雜,咱們會深刻地討論死鎖的解決、事件驅動模型、同步機制的底層實現、線程池源代碼解析等高級議題,幫助讀者知其然更知其因此然,再也無懼於多線程相關的問題。編程
多線程首先是屬於一種併發手段,因此咱們首先須要瞭解併發的基本概念。併發就是多個執行器同時執行不一樣的任務,若是這些任務須要訪問同一個數據,那麼就會產生數據競爭。若是不能作好併發控制,那麼數據競爭問題就有可能會致使程序最終的結果出現錯誤,也就是咱們常說的數據不一致。好比帳戶A同時要扣三筆錢,那麼若是三個線程同時執行扣款操做就有可能由於三個線程都用一開始的帳戶餘額減去一個值計算出三個結果並保存到帳戶餘額中,從而致使扣減結果之間的相互覆蓋。除了多線程併發以外還有更重要的分佈式併發主題,包括原子性、臨界區、互斥、補償、兜底任務等等專業術語,這些均可以在這篇不糾結於具體技術細節、只經過生活中的例子來說解併發概念的文章《當咱們在說「併發、多線程」,說的是什麼?》中找到答案。後端
瞭解了併發的基本概念以後咱們就能夠具體地在多線程編程領域中來了解具體的技術了。首先咱們先要了解,爲何會須要多線程?多線程到底解決的是什麼問題?而後,咱們就能夠開始實際動手寫真正的Java多線程編程代碼了,一開始,咱們會直接使用Thread
類來建立並運行線程。立刻咱們就碰到了多線程所帶來的問題,咱們必須經過線程同步機制才能保證最後的輸出結果正確。服務器
在《這一次,讓咱們徹底掌握Java多線程》這篇文章中,咱們從多線程使用的場景開始講起,只有弄明白了多線程到底能發揮什麼樣的做用咱們才能真正地在實踐中使用好這門重要的技術。以後咱們會使用Thread
來建立並運行線程,而後經過最基本的sychronized
關鍵字來實現臨界區的互斥訪問,實現這一系列文章中的第一個正確的Java多線程程序。多線程
但在實際的開發過程當中,咱們基本不會本身建立Thread
類表明的線程而後管理它的執行。相反,咱們把任務交給一個線程池,而後讓線程池本身管理任務的調度和線程的生命週期。線程池就像一個大管家,咱們只要給他設定好規則和預算,他就會自動幫咱們處理各類各樣的任務。想要使用好線程池,那麼你只須要看完《從0到1玩轉線程池》這篇文章就夠了!併發
多線程程序相比於單線程程序面臨更多更復雜的問題,這就像掏蜂窩同樣。咱們既想要蜂蜜的甘甜,可是又要時刻當心不要被蜇成了滿臉包。通常來講,多線程程序會面臨三類問題:正確性問題、效率問題、死鎖問題。分佈式
正確性是程序的核心,若是一個程序產出的結果多是錯誤的,那麼這個程序的價值必然大打折扣,甚至直接清零。咱們在以前的文章中使用synchronized
關鍵字處理過多線程併發中的數據競爭問題。可是在實際的開發過程當中,咱們還會碰到更多各式各樣的併發正確性問題。《多線程中那些看不見的陷阱》這篇文章中講到了synchronized
關鍵字、ReentrantLock
顯式鎖、CAS操做、volatile關鍵字等一系列的線程同步工具,相信有了這些工具的保駕護航,咱們必定能夠寫出大量正確的多線程程序。
雖然咱們能夠利用線程同步工具箱中的十八般兵器寫出正確的多線程程序,可是若是它執行得太慢甚至還比不上單線程程序的話那就得不償失了。因此咱們不只要「對」,還要在「對」的前提下更「快」才行。在《多線程加速指南》這篇文章中,咱們能夠利用CAS、ForkJoinPool
、線程封閉、java.util.concurrent
工具包等技術讓咱們的多線程程序的速度提高10倍、100倍甚至是1000倍。
死鎖問題相對來講比較特殊,由於一旦出現死鎖問題就會致使程序徹底沒法繼續執行。它既不會產生錯誤的結果,又由於程序會徹底中止因此已經不止是運行太慢的問題了。在各式各樣的併發程序中都會遇到死鎖問題,好比數據庫、操做系統等等都會有這個問題。若是是咱們的我的電腦,那麼死機以後重啓就能夠了,可是線上服務每每是不能中斷的,這就須要咱們找到更多更好的解決方案來解決不一樣狀況下的死鎖問題。相信讀完這篇文章《解決死鎖的100種方法》,你會對這個問題有更多的靈感。
講完了這麼多多線程相關的概念、技術與技巧,咱們也是時候下場練練手了。阻塞隊列不只是多線程編程中的重要工具,並且還使用了互斥鎖、條件變量、併發優化等等一系列重要的知識點來具體實現,這正是咱們練手的最佳素材。就讓咱們跟隨《從0到1實現本身的阻塞隊列》的腳步,一塊兒從0到1再到N,完成一個完整的JDK級別的阻塞隊列實現。
在看過多線程的基礎知識、關鍵技術,最後又完成了一次練手之後,咱們就能夠繼續深刻多線程領域中更深奧的高級主題了。
在以前的文章中,咱們已經掌握了線程池的使用方法,雖然線程池是一個稱職的管家,可是若是咱們不瞭解它的脾氣就有可能在不自覺的時候越過了一些它的底線,最後被它給狠狠地甩在了地上。那麼如今就讓咱們經過《線程池運行模型源碼全解析》來剖析線程池的運行模型,從源碼角度瞭解線程池究竟是怎麼運轉的。
咱們已經使用過了這麼多的線程同步機制,這些線程同步機制顯得那麼的神奇,幫助咱們躲開一個又一個的陷阱。那麼這些這麼厲害的東西究竟是怎麼實現的呢?這時候就要請出咱們的幕後英雄AbstractQueuedSynchronizer
(簡稱AQS)了。java.util.concurrent
中的大多數線程同步類都是基於AQS實現的,好比經常使用的就有可重入互斥鎖ReentrantLock
、閉鎖CountDownLatch
、可重入讀寫鎖ReentrantReadWriteLock
、信號量Semaphore
。在《同步機制的底層實現》中,咱們能夠一探究竟,看看AQS是如何實現這麼多風格迥異的線程同步機制的。
到這裏,咱們就完成了整個Java多線程知識體系之旅。在這個過程當中,咱們首先了解了併發的基本概念和Java多線程編程的基本方法,而後出現了線程池這個優秀的管家爲咱們打理好了任務執行與線程調度的全部麻煩事。以後咱們系統地瞭解並解決了多線程中的三類主要問題:正確性問題、效率問題和死鎖問題。在掌握了這麼多Java多線程編程的知識與技巧以後,咱們就經過實現一個阻塞隊列來了一次大練兵,不只能檢驗咱們的多線程編程技能,同時也加深了咱們對這些知識的理解。最後,咱們進入了多線程知識的深水區,經過JDK與Netty的成熟源代碼研究了三個更底層的高級主題:事件驅動模型、線程池運行模型、同步機制的底層實現。