Java併發編程之線程篇之線程的由來(一)

前言

在Java併發編程中線程的使用尤其重要。瞭解線程的由來,使用場景及注意事項是做爲一個合格的Java程序員必備的技能。本文章中會對線程的由來、進程與線程的區別、及線程的使用場景進行簡單介紹。但願經過該文章,小夥伴們能對線程有一個更深的瞭解。html

從操做系統發展瞭解線程

線程的出現,離不開進程。而進程的出現又離不開操做系統。操做系統的發展促進了線程與進程的技術崛起。因此瞭解操做系統的發展,對咱們理解線程尤其重要。整個操做系統大體分爲以下幾個階段:程序員

  • 手工操做
  • 單道批處理系統
  • 多道批處理系統
  • 分時操做系統
  • 等等等

在前三個階段中,對進程與線程的理解尤其重要,故文章會着重介紹前三個階段。算法

手工操做

最先的計算機並位出現真正意義上的操做系統,這時的計算機智能解決簡單的數學問題,好比正鉉,餘鉉等。其運行方式也特別簡單,程序員將對應於程序和數據的已穿孔的紙帶(或卡片)裝入輸入機,而後啓動輸入機把程序和數據輸入計算機內存,接着經過控制檯開關啓動程序針對數據運。計算完畢後,輸出機輸出計算結果;用戶取走結果並卸下紙帶(或卡片)後,才讓下一個用戶上機。舉個簡單的列子,假設咱們須要向計算機發送吃飯、洗澡、睡覺這三個指令,那麼在傳統計算機中,咱們能夠獲得下圖:編程

手動操做.png

從上圖中咱們能夠明顯看出,手工操做方式嚴重損害了系統的利用率。在等待用戶輸入指令時,計算機一直處於閒置狀態。segmentfault

單道批處理系統

爲了擺脫手動操做帶來的耗時性,實現做業(程序、數據、指令)的自動過渡。接着又出現了單道批處理系統。單道批處理系統在原來手動操做主要的區別是在輸入機與主機之間增長了一個存儲設備磁帶(盤)(下圖,紅色虛線部分),並在主機系統上配上監督程序,其具體運行方式一般是把一批做業以輸入到磁帶上,而後由監督程序將磁帶上的第一個做業裝入內存,並把運行控制權交給該做業。當該做業處理完成時,又把控制權交還給監督程序,再由監督程序把磁帶(盤)上的第二個做業調入內存。計算機系統就這樣自動地一個做業一個做業地進行處理,直至磁帶上的全部做業所有完成。仍是以上文吃飯、洗澡、睡覺這三個指令爲例子,咱們能夠獲得下圖:markdown

單道批處理系統.png

須要注意的是,雖然單道批處理操做系統可以解決手動操做時須要人工切換做業致使的系統利用率低的問題,可是又由於單道批處理系統是將做業一個一個加入內存的,那麼某一個做業由於等待磁帶(盤)或者其餘I/O操做而暫停時,那計算機就只能一直阻塞,直到該I/O完成。對於CPU操做密集型的程序,I/O操做相對較少,所以浪費的時間也不多。可是對於I/O操做較多的場景來講,CPU的資源是屬於嚴重浪費的。多線程

多道批處理系統

爲了解決單道批處理系統由於輸入/輸出(I/O)請求後,致使計算機等待I/O完成而形成的計算機的資源的浪費。接下來又出現了多道批處理系統。多道批處理系統與單道批處理系統的主要區別是在內存中容許一個或多個做業,當一個做業在等待I/O處理時,多批處理系統會經過相應調度算法調度另一個做業讓計算機執行。從而使CPU的利用率獲得更大的提升。以下圖所示:併發

多道批處理系統.png

進程的由來

在多道批處理系統中引伸出了一個很是重要的模式,即容許多個做業進入內存並運行。因爲在內存中存儲了多個做業,那麼多個做業如何進行區分?當某個做業由於等待I/O暫停時,怎麼恢復到以前的運行狀態呢?異步

因此這個時候,聰明的人們就發明了進程這一律念,用進程來保存每一個做業的數據與運行狀態,同時對每一個進程劃分對應的內存地址空間(代碼、數據、進程空間、打開的文件),而且要求進程只能使用它本身的內存空間。那麼就能夠達到做業的區分及恢復。分佈式

線程的由來

多道批處理系統所引入的進程的概念,確實提升了計算機的運行效率,可是單單使用進程來處理程序的併發的弊端也越來與突出,由於一個進程在一個時間段內只能作一件事情。若是某個程序有多個任務,只能逐個執行這些任務。

併發:宏觀上看起來同一個時間段有多個任務在計算機中執行。

仍是以上文提到的吃飯爲例子。假設在吃飯進程中包含兩個任務,一個看電視任務,一個吃飯任務。如今咱們但願計算機一邊執行吃飯任務,一邊執行看電視任務。那麼根據多道批處理系統的運行規則,計算機實際調度是以進程爲調度單位的,那麼咱們想一邊吃飯,一邊看電視的行爲,只能拆分爲兩個進程。可是咱們知道進程中存儲了大量信息(數據,進程運行狀態信息等)。那麼當計算機進行進程切換的時候,必然存在着很大的時間與空間消耗(由於每一個進程對應不一樣內存地址空間,進程的切換,實際是處理器處理不一樣的地址空間)。若是不拆分爲兩個進程,吃飯這兩個任務在同一個進程中,那麼這兩個任務必然是依次執行的,這又違背了咱們一邊看電視一邊吃飯的初衷。

那麼問題來了,咱們是否能夠實現進程中任務的切換,又能夠避免進程切換內存地址空間呢?聰明的人們又進一步的發明了線程這一律念。用線程表示進程中的不一樣任務,同時又將計算機實際調度的單元轉到線程。這樣就避免了進程的內存地址空間的切換,也達到了多任務的併發執行。具體以下圖所示:

調度區別.png

進程與線程的區別

前面講解了進程與線程的由來,有可能你們如今還有一絲疑惑,下面就讓咱們一塊兒來總結一下進程與線程的關係。

  • 進程是計算機分配資源的單元,而線程是計算機調度的基本單元。
  • 一個進程由一個或多個線程組成。線程表明着進程中不一樣的任務。
  • 進程之間相互獨立,同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等)及一些進程級的資源(如打開文件和信號)
  • 進程的切換由時空開銷。

進程與線程的關係以下圖所示:

進程線程的關係.png

線程的使用場景

線程的出現確實提升了程序運行的性能,那麼線程的使用場景有哪些呢?

  • 執行後臺任務,在不少場景中,可能會有一些定時的批量任務,好比定時發送短信、定時生成批量文件。在這些場景中能夠經過多線程的來執行。
  • 異步處理,好比在用戶註冊成功之後給用戶發送優惠券或者短信,能夠經過異步的方式來執行,一方面提高主程序的執行性能;另外一方面能夠解耦核心功能,防止非核心功能對核心功能形成影響。
  • 分佈式處理,好比fork/join,將一個任務拆分紅多個子任務分別執行。
  • BIO模型中的線程任務分發,也是一種比較常見的使用場景,一個請求對應一個線程。

線程使用注意事項

雖然在程序中咱們能夠建立多個線程來提升程序的運行效率與吞吐率,可是也會出現如下問題:

  • 因爲多個線程共同佔有所屬進程的資源和地址空間,那麼多個線程要同時訪問某個資源,這個時候該怎麼處理?
  • 線程既然能提升運行效率,那麼是否在程序中建立越多線程越好?

這些問題實際上是關於線程同步與合理的建立線程數的問題。這裏就不作過多的介紹,若是你們對這部分感興趣,能夠關注後續文章或查閱相關資料。

最後

站在巨人的肩膀上,才能看的更遠~

相關文章
相關標籤/搜索