JAVA併發模型
一、併發
併發程式是指在執行中有兩個及以上的任務同時在處理,與之相關的概念並行,是指在執行中有兩個及以上的任務同時執行,差別是在於處理和執行。在單核CUP中兩個及以上任務的處理方式是讓它們交替的進入CUP執行,這種對執行的處理方式就是併發。並行只能發生在多核CUP中,每個CUP核心拿到一個任務同時執行,並行是併發的一個子集
與序列程式相比並發程式設計的優點:
1):提高硬體資源的利用率(特別是IO資源),提高系統的響應速度、減少客戶端等待、增加系統吞吐量
2):解決特定領域的問題,比如GUI系統,WEB伺服器等。
如何實現併發:
1):程序併發,PHP(pcntl_fork)、PYTHON(multiprocessing)中很常見基於多程序併發。
2):執行緒併發,JAVA、C#以執行緒作為執行體進行併發。
3):協程併發,GOLANG、SCALA中基於協程的併發,雖然協程是線上程中程序排程的,但是協程有自己的暫存器和棧,排程切換完全在使用者態進行。可以簡單的理解協程與執行緒間是N:1的關係,所以協程避免了執行緒建立和上下文切換的系統開銷,理論上可以支援更大的併發量。
二、執行緒實現
在作業系統中執行緒是包含在程序中的消費資源較少、執行迅速的最小執行單元,根據作業系統核心是否對執行緒可感知,把執行緒分為核心執行緒和使用者執行緒。程式語言的執行緒實現都是基於這兩種執行緒之上。
1):基於核心執行緒(Kernel-Level Thread,KLT)
使用核心執行緒的一種高階介面--輕量級程序(Light Weight Process,LWP)實現的執行緒(通常意義上的執行緒),它與核心執行緒是一對一的關係。執行緒的建立,初始化,同步,切換(使用者態、核心態)都需要核心排程器(Scheduler)進行排程,消耗核心資源,每一個輕量級程序都需要一個核心執行緒對應,所以這執行緒能建立的數量是也是有限的。
2):基於使用者執行緒
建立在使用者空間的上的執行緒,核心對此無感知。執行緒的建立、排程在使用者態完成,不需要系統核心支援。由於沒有系統核心的支援,所有的執行緒操作都需要使用者程式自己處理。執行緒的建立、切換和排程都是需要考慮的問題,而且由於作業系統只把處理器資源分配到程序,如“阻塞如何處理”,“多處理器系統中如何將執行緒對映到其它處理器上”這類問題解決起來將會異常困難,甚至不可能完成。
3):基於使用者執行緒和核心執行緒混合
即使用核心執行緒(輕量級程序),也使用使用者執行緒。使用者執行緒依然建立在使用者空間上,執行緒的建立、排程、處理器對映能夠得到核心執行緒的支援,實現簡單。使用者執行緒與輕量級程序(核心執行緒)是N:M的對應關係,可以支援大規模的併發。
三、執行緒通訊
執行緒間通過協作才能合力完成一個任務,協作的基礎就是通訊。常用的執行緒間通訊的方式有兩種。
1):共享記憶體
設定一個共享變數,多個執行緒都可以對共享變數進行操作,共享變數就行通訊的中介。共享記憶體通訊方式實現簡單,資料的共享還使得執行緒間的通訊不需要資料的傳送,直接對記憶體進行訪問,加快了程式的執行效率。
但是多個執行緒操作同一個共享變數,勢必會造成“資料爭用”。競爭條件下必須讓共享變數進入臨界區進行保護,否則會產生資料不一致。
共享記憶體通訊過程是隱式的,但是同步操作是顯示的,開發者必須自行判斷何時進行執行緒互斥,何時對變數操作加鎖處理。
2):訊息傳遞
執行緒間通過傳遞資訊進行通訊。這種通訊模型實現起來複雜,執行緒之間沒有公共狀態,執行緒之間必須通過明確的傳送訊息來顯式進行通訊,訊息傳遞會產生系統開銷,控制訊息的有效性、先後關係,接到消如何通知執行緒處理,大資料量頻繁的傳遞如果控制效率等等問題的處理都要銷燬系統資源。
但是由於訊息的傳送必須在接收之前,不存在資料不一致的問題。
共享記憶體通訊過程是顯式的,但是同步是隱式進行的,開發者不需要進行臨界區判斷與執行緒互斥操作。
四、JAVA併發
JAVA1.2之前的執行緒是基於使用者執行緒實現的,之後的版本都是採用輕量級程序(核心執行緒)實現的。JVM中採用NPTL(Native POSIX Thread Library)機制,JVM本身不建立執行緒,使用作業系統提供的介面進行執行緒的排程和管理,從Thread類原始碼中可以看到執行緒啟動,中斷等方法都是native的。
圖一:Thread類原始碼
每一個JAVA執行緒都對應者一個核心執行緒,所以執行緒的建立、排程、上下文切換都需要系統核心的支援,會消耗系統資源。
JAVA執行緒間通訊是通過共享記憶體實現的,鎖線上程併發中有著舉足輕重地位,使用JAVA多執行緒時需要非常小心的處理同步問題。
“執行緒與鎖”模型是JAVA語言的併發模型。這也是大多數語言都支援的模型,由於其基本接近硬體本身執行的模式,可以解決的問題領域很多有著很高的執行效率,一直都是併發程式設計的首選。缺點是使用這樣模型需要開發者時刻警惕執行緒安全問題,處理複雜的執行緒協作問題,關注計算資源的開銷問題。
五:小結
JAVA併發程式設計需要面對兩個問題:
1):資源消耗問題,包括執行緒的建立、上下文切換對資源的消耗,鎖的互斥操作對資源的消耗,常用的解決方法有池化資源,根據計算型別保有適量執行緒,鎖優化策略等。
2):執行緒安全問題,要想讓併發程式正確的執行,需要解決原子性,可見性、有序性的問題,常用的保障執行緒安全的方法有加鎖、不共享狀態、不可變物件。
JAVA併發程式設計的支援:
1):JAVA記憶體模型(JMM Java Memory Model) ,通過final、volatile、synchronized的記憶體語義,happens-before原則,解決多執行緒中原子性,可見性、有序性問題。
2):JAVA併發程式設計包(J.U.C java.util.concurrent),大師之作,提供了更高效的鎖、更優化的併發資料結構、更方便的同步工具,更實用的執行緒池為高效的併發提供了有力的支援。
如果想學習Java工程化、高效能及分散式、深入淺出。效能調優、Spring,MyBatis,Netty原始碼分析的朋友可以加我的Java高階架構進階群:180705916,群裡有阿里大牛直播講解技術,以及Java大型網際網路技術的視訊免費分享給大家