教學筆記:併發之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的。

教學筆記:併發之JAVA併發模型

圖一: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併發模型


分享到:


相關文章: