目錄
- 1 執行緒安全定義
- 2 Java資料與執行緒安全
- 2.1 不可變
- 2.2 絕對執行緒安全
- 2.3 相對執行緒安全
- 2.4 執行緒兼容
- 2.5 執行緒對立
- 3 Java執行緒安全支持
- 3.1 互斥同步
- 3.1.1 synchronized互斥同步原理
- 3.1.2 Lock介面和ReentrantLock互斥同步原理
- 3.1.3 synchronized和Lock對比
- 3.2 非阻塞同步
- 3.3 無同步方案
- 3.3.1 可重入代碼
- 3.3.2 執行緒本地存盤
- 3.1 互斥同步
1 執行緒安全定義
含糊的定義:如果一個物件可以安全地被多個執行緒同時使用,那它就是執行緒安全的
嚴謹的定義:
當多個執行緒同時訪問一個物件時,如果不用考慮這些執行緒在運行時環境下的調度和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為都可以獲得正確的結果,那就稱這個物件是執行緒安全的,-----From《Java并發編程實戰》作者Brian Goetz
2 Java資料與執行緒安全
從執行緒安全角度,將Java中各種操作共享的資料分為:不可變、絕對執行緒安全、相對執行緒安全、執行緒兼容和執行緒對立
2.1 不可變
不可變的物件一定是執行緒安全,
如何保證不可變呢?
- 基本資料型別,用final修飾
- 物件型別:用final修飾物件中可變的欄位
Java中常用的不可變物件:String、Number的部分子類如 Long、Double、BigInteger、BigDecimal等,
為什么String不可變,參考:基本資料型別及String
2.2 絕對執行緒安全
ConcurrentHashMap (我后續會更新ConcurrentHashMap原始碼分析專題)
2.3 相對執行緒安全
定義:只能保證物件單次的操作是執行緒安全,連續呼叫不能保證執行緒安全
大部分聲稱執行緒安全的類都屬于這種型別,例如Vector、HashTable、Collections的 synchronizedCollection()方法包裝的集合等,
2.4 執行緒兼容
定義:物件本身并不是執行緒安全的,但是可以通過在呼叫端使用同步手段來保證物件在并發環境中可以安全地使用,如集合類ArrayList和HashMap等,
2.5 執行緒對立
定義:不管呼叫端是否采取了同步措施,都無法在多執行緒環境中并發使用代碼,
例子:Thread類的suspend()和resume()方法:如果有兩個執行緒同時持有一個執行緒物件,一個嘗試去中斷執行緒,一個嘗試去恢復執行緒,在并發進行的情況下,無論呼叫時是否進行了同步,目標執行緒都存在死鎖風險
3 Java執行緒安全支持
JVM同步機制和鎖類別庫實作執行緒安全
3.1 互斥同步
同步:在多個執行緒并發訪問共享資料時,保證共享資料在同一個時刻只被一條執行緒使用
互斥:實作同步的一種手段,
互斥同步性能開銷:互斥同步屬于一種悲觀的并發策略,無論共享的資料是否真的會出現競爭,它都會進行加鎖,引發:用戶態到核心態轉換、維護鎖計數器、檢查是否有被阻塞的執行緒需要被喚醒 等開銷
3.1.1 synchronized互斥同步原理
- synchronized關鍵字經過Javac編譯之后,會在同步塊的前后分別形成monitorenter和monitorexit這兩個位元組碼指令,
- 執行monitorenter指令時,首先要去嘗試獲取物件的鎖,如果這個物件沒被鎖定,或者當前執行緒已經持有了那個物件的鎖,就把鎖的計數器的值增加1
- 執行 monitorexit指令時會將鎖計數器的值減1,一旦計數器的值為零,鎖隨即就被釋放
- 如果獲取物件鎖失敗,那當前執行緒阻塞等待,直到鎖被釋放,
關于同步塊的說明:
monitorenter和monitorexit指令,都需要一個reference型別的引數,指明要鎖定和解鎖的物件,synchronized修飾地方不同,reference取不同的值:
- 修飾 物件,取這個物件的參考作為reference;
- 修飾 實體方法,取方法所屬物件實體作為reference,
- 修飾 類方法,取Class物件來作為執行緒要持有的鎖
根據兩個monitorenter和monitorexit這兩個位元組碼指令執行程序,可以得出以下推論:
- 被synchronized修飾的同步塊對同一條執行緒來說是可重入的
- 被synchronized修飾的同步塊在持有鎖的執行緒執行完畢并釋放鎖之前,會無條件地阻塞后面其他執行緒的進入
3.1.2 Lock介面和ReentrantLock互斥同步原理
參考:JUC鎖: LockSupport詳解 JUC鎖: ReentrantLock詳解 這兩個專題講解
3.1.3 synchronized和Lock對比
Lock應該確保在finally塊中釋放鎖,否則一旦同步代碼塊中拋出例外,則有可能永遠不會釋放持有的鎖,Lock必須由程式員來保證鎖釋放,而synchronized由Java虛擬機來確保即使出現例外,鎖也能被自動釋放,
3.2 非阻塞同步
非阻塞同步:樂觀并發策略:不管風險,先進行操作,如果沒有其他執行緒爭用共享資料,那操作就直接成功了,如果共享的資料的確被爭用,產生了沖突,那再進行其他的補償措施,最常用的補償措施是不斷地重試,直到出現沒有競爭的共享資料為止,樂觀并發策略的實作不再需要把執行緒阻塞掛起,
利用處理器指令,實作非阻塞同步
硬體保證某些從語意上看起來需要多次操作的行為可以只通過一條處理器指令就能完成,這類指令常用的有:
- 測驗并設定(Test-and-Set)
- 獲取并增加(Fetch-and-Increment)
- 交換(Swap)
- 比較并交換(Compare-and-Swap :CAS)
- 加載鏈接/條件儲存(Load-Linked/Store-Conditional:LL/SC)
CAS的專題分析:JUC原子類: CAS, Unsafe和原子類詳解
3.3 無同步方案
如果方法不涉及共享資料,那自然不需要采用同步措施來保證其正確性,因此會有一些代碼天生就是執行緒安全的
3.3.1 可重入代碼
代碼定義:可以在代碼執行的任何時刻中斷它,轉而去執行另外一段代碼(包括遞回呼叫它本身),而在控制權回傳后,原來的程式不會出現任何錯誤,也不會對結果有所影響,
特征:
- 不依賴全域變數、堆資料、公用的系統資源
- 用到的狀態量都由引數中傳入
- 不呼叫非可重入的方法
3.3.2 執行緒本地存盤
代碼定義:共享資料保證只在同一個執行緒中執行
場景:消費佇列的架構模式(如“生產者-消費者”模式)都會將產品的消費程序限制在一個執行緒中消費完,
如果變數要被多執行緒訪問,使用volatile關鍵字將它宣告為“易變的”;如果變數執行緒獨享,通過java.lang.ThreadLocal類來實作執行緒本地存盤的功能,
ThreadLocal專題分析:Threadlocal原始碼解讀
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/540118.html
標籤:其他
