您好,登錄后才能下訂單哦!
關注我的架構技術公眾號:“架構師修煉寶典”
一周出產1-2篇技術文章,希望在你的架構技術路上有我的點滴陪伴!
作為一個合格的Java程序員,必須要對并發編程有一個深層次的了解,在很多互聯網企業都會重點考察這一塊。可能很多工作3年以上的Java程序員對于這一領域幾乎沒有太多研究。所以在接下來內容中,我會將并發編程整個領域由淺到深做非常全面的分析。
內容導航
從操作系統的發展了解進程、線程模型
線程的優勢
線程的生命周期
線程的應用場景
了解進程、線程模型
每次學習一個新技術,我會先去了解這個技術的背景,這個過程看似浪費時間,其實在后續的學習過程中,能夠促進理解很多問題。所以對于線程這個概念,我會先從操作系統講起。因為操作系統的發展帶來了軟件層面的變革。 從多線程的發展來看,可以操作系統的發展分為三個歷史階段:
真空管和穿孔卡片
晶體管和批處理系統
集成電路和多道程序設計
最早的計算機只能解決簡單的數學運算問題,比如正弦、余弦等。運行方式:程序員首先把程序寫到紙上,然后穿孔成卡票,再把卡片盒帶入到專門的輸入室。輸入室會有專門的操作員將卡片的程序輸入到計算機上。計算機運行完當前的任務以后,把計算結果從打印機上進行輸出,操作員再把打印出來的結果送入到輸出室,程序員就可以從輸出室取到結果。然后,操作員再繼續從已經送入到輸入室的卡片盒中讀入另一個任務重復上述的步驟。
操作員在機房里面來回調度資源,造成計算機存在大量的空閑狀態 。而當時的計算機是非常昂貴的,人們為了減少這種資源的浪費。就采用了 批處理系統來解決
批處理操作系統 的運行方式:在輸入室收集全部的作業,然后用一臺比較便宜的計算機把它們讀取到磁帶上。然后把磁帶輸入到計算機,計算機通過讀取磁帶的指令來進行運算,最后把結果輸出磁帶上。批處理操作系統的好處在于,計算機會一直處于運算狀態,合理的利用了計算機資源。(運行流程如下圖所示)
(注:此圖來源于現代操作系統)
批處理操作系統雖然能夠解決計算機的空閑問題,但是當某一個作業因為等待磁盤或者其他I/O操作而暫停,那CPU就只能阻塞直到該I/O完成,對于CPU操作密集型的程序,I/O操作相對較少,因此浪費的時間也很少。但是對于I/O操作較多的場景來說,CPU的資源是屬于嚴重浪費的。
多道程序設計 的出現解決了這個問題,就是把內存分為幾個部分,每一個部分放不同的程序。當一個程序需要等待I/O操作完成時。那么CPU可以切換執行內存中的另外一個程序。如果內存中可以同時存放足夠多的程序,那CPU的利用率可以接近100%。 在這個時候,引入了第一個概念- 進程 , 進程的本質是一個正在執行的程序,程序運行時系統會創建一個進程,并且給每個進程分配獨立的內存地址空間保證每個進程地址不會相互干擾。同時,在CPU對進程做時間片的切換時,保證進程切換過程中仍然要從進程切換之前運行的位置出開始執行。所以進程通常還會包括程序計數器、堆棧指針。
有了進程以后,可以讓操作系統從宏觀層面實現多應用并發。而并發的實現是通過CPU時間片不端切換執行的。對于單核CPU來說,在任意一個時刻只會有一個進程在被CPU調度
有了進程以后,為什么還會出現線程呢?
在一個應用進程中,會存在多個同時執行的任務,如果其中一個任務被阻塞,將會引起不依賴該任務的任務也被阻塞。舉個具體的例子來說,我們平常用word文檔編輯內容的時候,都會有一個自動保存的功能,這個功能的作用是,當計算機出現故障的情況下如果用戶未保存文檔,則能夠恢復到上一次自動保存的點。假設word的自動保存因為磁盤問題導致寫入較慢,勢必會影響到用戶的文檔編輯功能,直到磁盤寫入完成用戶才可編輯,這種體驗是很差的。如果我們把一個進程中的多個任務通過線程的方式進行隔離,那么按照前面提到的進程演進的理論來說,在單核心CPU架構中可以通過CPU的時間片切換實現線程的調度充分利用CPU資源以達到最大的性能。加Q群:725219329可獲取一份Java架構進階技術精品視頻。(高并發+Spring源碼+JVM原理解析+分布式架構+微服務架構+多線程并發原理+BATJ面試寶典)
我們用了比較長的篇幅介紹了進程、線程發展的歷史。總的來說是人們對于計算機的要求越來越高;對于計算機本身的資源的利用率也在不斷提高。
線程的優勢
前面分析了線程的發展歷史,這里簡單總結一下線程有的優勢如下
線程可以認為是輕量級的進程,所以線程的創建、銷毀要比進程更快
從性能上考慮,如果進程中存在大量的I/O處理,通過多線程能夠加快應用程序的執行速度(通過CPU時間片的快速切換)。
由于線程是CPU的最小調度單元,所以在多CPU架構中能夠實現真正的 并行 執行。每一個CPU可以調度一個線程
這里有兩個概念很多人沒有搞明白,就是并行和并發
并行 :同時執行多個任務,在多核心CPU架構中,一個CPU核心運行一個線程,那么4核心CPU,可以同時執行4個線程
并發 :同處理多個任務的能力,通常我們會通過TPS或者QPS來表示某某系統支持的并發數是多少。
總的來說,并行是并發的子集。也就是說我們可以寫一個擁有多線程并行的程序,如果在沒有多核心CPU來執行這些線程,那就不能以并行的方式來運行程序中的多個線程。所以并發程序可以是并行的,也可以不是。 Erlang之父Joe Armstrong通過一張圖型的方式來解釋并發和并行的區別,圖片如下
線程的生命周期
線程是存在生命周期的,從線程的創建到銷毀,可能會經歷6種不同的狀態,但是在一個時刻線程只能處于其中一種狀態
NEW:初始狀態,線程被創建時候的狀態,還沒有調用start方法
RUNNABLE:運行狀態,運行狀態包含就緒和運行兩種狀態,因為線程啟動以后,并不是立即執行,而是需要通過調度去分配CPU時間片
BLOCKED:阻塞狀態,當線程去訪問一個加鎖的方法時,如果已經有其他線程獲得鎖,那么當前線程會處于阻塞狀態
WAITING:等待狀態,設置線程進入等待狀態等待其他線程做一些特定的動作進行觸發
TIME_WAITING:超時等待狀態,和WAITING狀態的區別在于超時以后自動返回
TERMINATED:終止狀態,線程執行完畢
下圖整理了線程的狀態變更過程及變更的操作,每一個具體的操作原理,我會在后續的文章中進行詳細分析。
這里有一個問題大家可能搞不明白,BLOCKED和WAITING這兩個阻塞有什么區別?
BLOCKED狀態是指當前線程在等待一個獲取鎖的操作時的狀態。
WAITING是通過Object.wait或者Thread.join、LockSupport.park等操作實現的
BLOCKED是被動的標記,而WAITING是主動操作
如果說得再深入一點,處于WAITING狀態的線程,被喚醒以后,需要進入同步隊列去競爭鎖操作,而在同步隊列中,如果已經有其他線程持有鎖,則線程會處于BLOCKED狀態。所以可以說BLOCKED狀態是處于WAITING狀態的線程重新喚醒的必經的狀態
線程的應用場景
線程的出現,在多核心CPU架構下實現了真正意義上的并行執行。也就是說,一個進程內多個任務可以通過多線程并行執行來提高程序運行的性能。那線程的使用場景有哪些呢?
執行后臺任務,在很多場景中,可能會有一些定時的批量任務,比如定時發送短信、定時生成批量文件。在這些場景中可以通過多線程的來執行
異步處理,比如在用戶注冊成功以后給用戶發送優惠券或者短信,可以通過異步的方式來執行,一方面提升主程序的執行性能;另一方面可以解耦核心功能,防止非核心功能對核心功能造成影響
分布式處理,比如fork/join,將一個任務拆分成多個子任務分別執行
BIO模型中的線程任務分發,也是一種比較常見的使用場景,一個請求對應一個線程。加Q群:725219329可獲取一份Java架構進階技術精品視頻。(高并發+Spring源碼+JVM原理解析+分布式架構+微服務架構+多線程并發原理+BATJ面試寶典)
關注我的架構技術公眾號:“架構師修煉寶典”
一周出產1-2篇技術文章,希望在你的架構技術路上有我的點滴陪伴!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。