關于JAVA并發
一談到Java并發編程,我們一般就會聯想起進程、線程、并行、并發等等概念。那么這些概念都代表什么呢?進程與線程有什么關系?并發與并行又是什么關系呢?
進程與線程
進程是指程序的一次動態執行過程,通常我們說計算機中正在執行的程序就是進程,每個程序都會對應著一個進程。一個進程包含了從代碼加載到執行完成的一個完整過程,它是操作系統資源分配最小單元。
而線程則是比進程更小的執行單位,是CPU調度和分派的基本單位。每個進程至少有一個線程,反過來一個線程只能屬于一個進程,線程可以對進程所有的資源進行調度和運算。線程既可以由操作系統內核來控制調度,也可以由用戶程序進行控制調度。
根據下圖可以看到,多個CPU會去執行這三個進程。其中每個進程都包含著至少一個線程,比如進程1包含了四個線程,進程2和進程3包含一個線程。此外,每個進程都有自己的資源,而進程內的所有線程都共享進程包含的資源。
進程與線程
并發與并行
并發和并行都可以是相對于進程或是線程來說。并發是指一個或若干個CPU對多個進程或線程之間進行多路復用,用簡單的語言來說就是CPU輪著執行多個任務,每個任務都執行一小段時間,從宏觀上看起來就像是全部任務都在同時執行一樣。并行則是指多個進程或線程同一時刻被執行,這是真正意義上的同時執行,它必須要有多個CPU的支持。如下圖是并發和并行的執行時間圖。對于并發來說,線程一線執行一段時間,然后線程二再執行一段時間,接著線程三再執行一段時間。每個線程都輪流得到CPU的執行時間,這種情況下只需要一個CPU即能夠實現。對于并行來說,線程一、線程二和線程三是同時執行的,這種情況下需要三個CPU才能實現。并發和并行都提升了CPU的資源利用率。
并發與并行
而對于Java并發,就是在Java平臺上實現來實現并發機制,Java平臺上提供了線程以及線程并發
多線程能提升執行效率
前面我們了解到多線程可以實現并發和并行執行,所以多線程能提升總體的效率。如果不支持多線程的話,那么當某個執行任務進入等待阻塞狀態時,則可能因為阻塞而導致運行效率低下。如下圖一中,一個請求任務發起請求后則開始等待響應,此時該線程占著CPU又不干活,從整個運行線上可以看到真正運行(綠色方塊)的時間很少,這就是運行效率低下。但在如果支持多線程并發的情況下,則可以在等待阻塞時去干其它的活。此外,多CPU環境下,如果一個任務能夠分解成多個小任務,那么就能夠用多個CPU同時執行它,這樣就能以更加快的速度完成任務,畢竟單個CPU運行能力有限。如下圖二中,一旦將任務分解成三個小任務后,在多CPU環境下則能夠并行執行,大大減少了整體執行時間。
單線程阻塞狀態
多線程并行
多線程能提升用戶體驗
多線程也能提升用戶體驗,如果一個線程的任務既包含耗時的任務又包含用戶交互的任務,那么則可能會導致用戶體驗很糟糕。如下圖,假如大家看到這些窗口一直在打轉又無法對其進行操作,是不是很難受?一個線程發起請求后開始等待請求結果,用戶界面則一直卡著沒響應。我們可以通過多線程將任務分為請求任務和界面操作兩部分,這樣就能在請求后保持對界面操作的響應,以便提供更好的用戶體驗。
界面卡住
多線程讓編碼更難
天下沒有免費的午餐,多線程也是需要付出代價的。從編寫代碼的角度來看,多線程使得編碼變得更加復雜,本質上這是因為多線程機制與現代計算機結構所帶來的。縱使在編程語言設計專家的努力下,現在有很多簡化多線程編程的語言和模型,但相比于單線程來說多線程的編寫仍然復雜很多。數據從主存儲到CPU中間有若干層緩存和寄存器,而且多個線程可能訪問共享內存,這就涉及到數據同步問題,從而增加了多線程編程的復雜性。此外,線程與線程之間的通信也比較麻煩,這也增加了多線程編碼的復雜性。總之,盡管很多編程語言嘗試為我們提供更便捷的多線程編程,但在語言層面仍然無法完全屏蔽掉多線程與計算機結構的復雜性,所以不管我們使用什么語言都需要為多線程的編碼考慮得更多。
上下文切換與資源開銷
多線程除了增加編碼難度外,它還在執行過程中帶來實際的損耗,包括資源開銷和上下文切換開銷。資源開銷主要包括其本身占用的內存資源、執行時線程本地棧開銷以及對這些線程進行管理的開銷。而上下文切換開銷則是因為CPU由一個線程切換到另外一個線程是需要做現場保護和現場恢復工作,包括線程標識、寄存器內存、線程狀態、線程優先級、線程資源清單等等。如下圖所示,假設線程一和線程二由某個CPU執行。線程一執行一段時間后將相關信息保存到現場數據結構中,而線程數據結構存放在主存儲中,然后從線程二對應的現場數據結構中恢復線程二相關信息,完成現場恢復后線程二開始執行。接下去的過程反過來,由線程二切換到線程一。其中可以看到由虛線分割的兩部分被標為切換開銷,從完整的時序來看,這兩部分并非執行線程的任務,而是消耗在了現場的保護和恢復上了,這便是上下文切換的開銷。
上下文切換
在實踐中我們要綜合考慮多線程的優缺點,不能一味的去追求多線程,在使用多線程之前我們必須去衡量多線程帶來的好處與代價。
更多Java并發原理可關注作者下面的專欄:
作者簡介:筆名seaboat,擅長人工智能、計算機科學、數學原理、基礎算法。出版書籍:圖解數據結構與算法、Tomcat內核設計剖析、圖解Java并發原理、人工智能原理科普。