【Java併發】執行緒安全性
執行緒安全性
定義:當多個執行緒訪問某個類時,不管執行時環境採用 何種排程方式 或者這些執行緒將如何交替執行,並且在主調程式碼中 不需要任何額外的同步或協同 ,這個類都能表現出 正確的行為 ,那麼就稱這個類是執行緒安全的。
執行緒安全性主要體現在三個方面:原子性、可見性、有序性:
- 原子性 :提供了互斥訪問,同一時刻只能有一個執行緒來對它進行操作
- 可見性 :一個執行緒對主記憶體的修改可以及時地被其他執行緒觀察到
- 有序性 :一個執行緒觀察其他執行緒中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序
原子性
原子性在 JDK 中主要由兩個方面體現出來:
Atomic
一個是 JDK 中已經提供好的 Atomic 包,它們均使用了 CAS 完成執行緒的原子性操作(詳見 【Java併發】淺析 AtomicLong & LongAdder )。
另一個是使用鎖的機制來處理執行緒之間的原子性。鎖主要包括:synchronized、lock。
synchronized
依賴於 JVM 去實現鎖,因此在這個關鍵字作用物件的作用範圍內,都是同一時刻只能有一個執行緒對其進行操作的。synchronized 是 Java 中的一個關鍵字,是一種同步鎖。它可以修飾的物件主要有四種:
- 修飾程式碼塊:大括號括起來的程式碼,作用於 呼叫的物件
- 修飾方法:整個方法,作用於 呼叫的物件
- 修飾靜態方法:整個靜態方法,作用於 所有物件
- 修飾類:括號括起來的部分,作用於 所有物件
注意:如果當前類是一個父類,子類呼叫父類的被 synchronized 修飾的方法,不會攜帶 synchronized 屬性,因為 synchronized 不屬於方法宣告的一部分。
Lock
首先要說明的就是 Lock,通過檢視 Lock 的原始碼可知,Lock 是一個介面。ReentrantLock 是唯一實現了 Lock 介面的類,意思是“可重入鎖”,並且 ReentrantLock 提供了更多的方法。
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
鎖的分類
-
可重入鎖
-
synchronized
/ReentrantLock
-
-
可中斷鎖
-
synchronized
不可中斷,Lock
可中斷
-
-
公平鎖
-
synchronized
非公平鎖 -
ReentrantLock
和ReentrantReadWriteLock
預設情況下非公平鎖,可設定為公平鎖
-
-
讀寫鎖
-
ReadWriteLock
/ReentrantReadWriteLock
-
可見性
導致共享變數線上程間不可見的原因:
- 執行緒交叉執行
- 重排序結合線程交叉執行
- 共享變數更新後的值沒有在工作記憶體與主存間及時更新
JVM 對於可見性,提供了 synchronized 和 volatile:
synchronized
JMM 關於 synchronized 的兩條規定:
- 執行緒解鎖前,必須把共享變數的最新值重新整理到主記憶體
- 執行緒加鎖時,將清空工作記憶體中共享變數的值,從而使用共享變數時需要從主記憶體中重新讀取最新的值(注意: 加鎖與解鎖是同一把鎖 )
volatile
volatile 的方式是:通過加入 記憶體屏障 和 禁止重排序 優化來實現。
- 對 volatile 變數寫操作時,會在寫操作後加入一條 store 屏障指令,將本地記憶體中的共享變數值重新整理到主記憶體。
- 對 volatile 變數讀操作時,會在讀操作前加入一條 load 屏障指令,從主記憶體中讀取共享變數。
- volatile的屏障操作都是 cpu 級別的;適合狀態驗證,不適合累加值,volatile關鍵字不具有原子性。
- 適合狀態驗證,不適合累加值,volatile關鍵字不具有原子性
有序性
Java 記憶體模型中,允許編譯器和處理器對指令進行 重排序 ,但是重排序過程不會影響到 單執行緒 程式的執行,卻會影響到多執行緒併發執行的正確性。而 Java 提供了 volatile、synchronized、Lock ,它們可以用來保證有序性。
另外,Java 記憶體模型具備一些先天的有序性,即不需要任何手段就能得到保證的有序性。通常被我們稱為happens-before 原則(先行發生原則)。如果兩個執行緒的執行順序無法從 happens-before 原則推匯出來,那麼就不能保證它們的有序性,虛擬機器就可以對它們進行重排序。
【以下規則摘抄自 《深入理解Java虛擬機器》 】
- 程式次序規則:一個執行緒內,按照程式碼順序,書寫在前面的操作先行發生於書寫在後面的操作
- 鎖定規則:一個unlock操作先行發生於後面對同一個鎖的lock操作
- volatile變數規則:對一個變數的寫操作先行發生於後面對這個變數的讀操作(重要)
- 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C
- 執行緒啟動規則:Thread物件的start()方法先行發生於此執行緒的每一個動作
- 執行緒中斷規則:對執行緒interrupt()方法的呼叫先行發生於被中斷執行緒的程式碼檢測到中斷事件的發生
- 執行緒終結規則:執行緒中所有的操作都先行發生於執行緒的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到執行緒已經終止執行
思維導圖
筆記整理自: 【IMOOC】Java併發程式設計與高併發解決方案