java多執行緒(一)
何日請纓提銳旅,一鞭直渡清河洛。
概述
程式 = 伺服器、PC或Mac上同時執行的多個應用程式 執行緒 = 在一個程式中執行多個任務
程式
程式——當一個軟體應用程式開始執行時,它會使用系統資源,如I/O
裝置、CPU
、RAM
、HDD
,可能還有網路資源。類似地,其他軟體應用程式也希望同時使用相同的系統資源。為了讓多個軟體應用程式共享系統資源,它們之間必須有明確的邊界。否則,它們執行時會相互影響。通過程式來實現這種隔離。由於多個程式可以同時執行,一個作業系統通過程式執行多個任務!
使用
CPU
,記憶體,磁碟和網路在OS
中執行的程式
執行緒
執行緒—軟體應用程式(例如Word
檔案)可能不得不在處理使用者事件和儲存當前工作之間進行多工處理。為了實現這一目標,每個任務都需要訪問該程式可用的相同系統資源,但要在其自己的空間內。與OS
相似,每個程式都可以通過執行緒為執行於其中的多個任務提供隔離。由於多個執行緒可以在程式中同時執行,因此一個程式通過執行緒執行多工!
在
Java
程式(Java
虛擬機器器)中執行的執行緒
單執行緒與多執行緒應用程式
單執行緒應用
打個比方:一家有8名成員的廚房部門的餐廳在任何給定時間都只能提供一張桌子,因為他們只有一名服務員。如果有更多客人,他們只能在大廳等候。
缺點
- 在大廳等候的客人肯定很沮喪。
- 八個人在廚房,浪費資源。
優點
- 複雜性降低,一次指接待一批客人。
單執行緒應用
執行時間
多執行緒應用
一家有8名成員的廚房部門的餐廳在任何給定時間都只能提供16張桌子,因為他們有4個服務員。 優點
- 客人的等待時間打打減少
- 充分合理的利用資源
缺點
- 資源共享到時業務複雜
由於餐廳現在引入了更多的服務員(執行緒),因此它正在有效地利用廚房(
CPU
)人員(核心)。結果,它在任何給定時間服務於更多的來賓(使用者)
這同樣適用於軟體應用程式。由於併發執行,本質上導致了響應能力的顯著改善。
同樣,可以拆分為多個獨立子任務的長時間執行的任務可以在多個執行緒中並行執行,從而顯著提高了應用程式的效能。
多執行緒應用
執行時間
併發帶來的響應和並行引起的效能是多執行緒應用程式的動機。
堆和棧
上面我們編寫了一個由執行緒執行的程式碼塊。Java
應用程式啟動時,Java
程式會生成主執行緒(main
方法),這是應用程式的入口點。從現在開始,整個應用程式邏輯要麼在主執行緒中執行,要麼在我們從應用程式中派生的執行緒中執行,以實現併發執行
執行緒執行的應用程式邏輯在CPU中執行計算,計算結果則儲存在RAM
中。
每個執行緒將等待輪流使用一個CPU
核心執行計算,並等待一個本地記憶體區域(每個方法呼叫的棧和棧中的多個棧幀)臨時儲存計算結果。執行緒完成程式碼執行後,通常會將結果重新整理回RAM
(堆)。
堆是所有物件所在的執行緒之間的共享記憶體區域。 堆疊是分配給每個正在執行的執行緒的專用記憶體區域。 堆記憶體是垃圾收集器可直接回收的,通過刪除應用程式中不再使用(引用)的物件來釋放空間,而棧持有的記憶體空間在執行執行緒完成後被釋放
堆
在Java
應用程式中建立的所有物件都在堆的記憶體中分配了空間。只要從應用程式中的某處引用它,這些物件就存在。
棧
執行某個方法或呼叫一系列方法的執行緒將需要記憶體中的空間來儲存區域性變數和方法引數——這個記憶體區域稱為棧。執行緒呼叫的每個方法都堆疊在前面的方法之上——呼叫稱為棧幀。
鎖
monitor
和鎖
當一個物件及其狀態被多個執行緒共享時,對該狀態所做的任何修改(例如。網頁計數器),必須以原子操作執行。否則,物件的狀態將被併發修改破壞。原子性是通過使用鎖來保護關鍵程式碼塊來實現的,從而在相互競爭的執行緒之間強制互斥。
每個物件都有一個稱為monitor
的固有鎖。由於這種語言規定,通過向方法簽名中新增關鍵字synchronized
,可以很容易地鎖定關鍵程式碼塊。在下面的程式中,安全物件的open()
方法可以被執行緒訪問,只有在獲取了內部鎖之後——注意方法簽名中的synchronized
關鍵字。
通過獲取內部鎖依次訪問安全物件的執行緒
執行同步方法的執行緒被認為獲得了物件的鎖。在此執行緒完成方法的執行之前,沒有其他執行緒可以執行此物件的這個或任何其他同步方法。
但是已經可以訪問該物件鎖的執行緒可以呼叫其他同步方法,而無需重新獲取該鎖。此機制稱為重入鎖定或重入同步。
鎖或同步的另一個重要方面是建立happens-before
關係。也就是說,在synchronized
塊中對物件的狀態所做的任何修改都保證對其他執行緒可見,這些執行緒隨後將通過獲取相同的鎖來訪問相同的狀態。
獲取物件鎖的方法有兩種
- 通過在方法上新增
synchronized
關鍵字 - 使用
synchronized
同步程式碼塊
使用同步程式碼塊是首選方法,因為它不會阻塞所有例項方法,而只會阻塞要防止併發訪問的阻塞,從而提高了效能。
例項方法的同步物件是
this
當前物件 靜態方法的同步物件是當前物件的class
類
實際上,同步(或加鎖)的作用是防止多執行緒應用程式中出現以下錯誤情況
- 執行緒幹擾——多個執行緒同時修改一個例項的狀態,並在程式中破壞它,因為相互競爭的執行緒計劃執行的時間不同,從而導致它們無法按順序執行同一組操作。
- 記憶體不一致錯誤——即使寫執行緒在讀取之前完成了執行,讀執行緒也會看到過時的資料。這是因為寫執行緒會將更改後的值儲存在
CPU
快取中,而不是將其重新整理到主記憶體中,其他所有執行緒都可以在主記憶體中看到更新後的狀態。
當競爭執行緒修改/修改或修改/讀取共享的可變狀態時,如果沒有適當的同步(涉及非原子操作),則由於交織而導致“競爭條件”和“記憶體不一致錯誤”
總結
- 作業系統將
CPU
、RAM
、HDD
、網路裝置等硬體資源提供給軟體應用程式進行計算。 - 這些資源在作業系統中作為程式同時執行的多個應用程式之間共享。作業系統的強大之處在於其通過程式執行多工的能力。
- 同樣,作為程式執行的單個軟體應用程式必須執行多工,以便有效利用硬體資源。程式的能力在於它可以通過併發執行的執行緒來執行多工。
- 通過併發性(例如。:一個
servlet
同時服務多個使用者),通過並行的效能(例如。:並行呼叫多個HTTP
端點來服務一個使用者請求)是多執行緒應用程式的動機。 - 執行緒在
CPU
核心中執行。任何應用程式程式碼都線上程中執行。啟動Java
應用程式時,JVM從主執行緒內呼叫main()
方法。 - 堆是分配給所有執行緒以儲存物件的公共記憶體區域。任何引用堆中物件的執行緒都可以讀取/修改該物件。
- 棧是分配給每個執行緒的私有記憶體區域(例如:兩個執行緒同時呼叫一個通用實用程式方法將在其自己的棧中執行該方法,其中一個執行緒的區域性變數和方法引數對另一個執行緒不可見)
- 獲取公共鎖後,訪問堆中同一物件的多個執行緒必須同步執行此操作,以防止破壞物件的狀態。