Synchronized ,Volatile,Lock 三者不可告人的祕密

輝先森談程式設計發表於2020-10-03

 

 

前言

傳說天地初開,Synchronized ,Volatile,Lock孕育而生,他們之間有著怎麼樣的精彩故事呢?這篇文章,輝先森與你一同看看。


 

一、Synchronized ,Volatile,Lock出現的場景

他們的出現是為了解決執行緒安全問題。注意了,執行緒安全不是指執行緒的安全,而是指記憶體的安全,所以多執行緒的通訊要保證的也是記憶體資料的安全。簡單的說明一下原因:

在目前主流的作業系統中,每個程式都有自己的記憶體空間,而不能去訪問其他程式的,這是有作業系統進行保證的啊,就是每個程式都有相同的邏輯空間,但是資料怎麼存在真實的物理空間裡面,這是有我們的作業系統去解決(分頁,分段)。

執行緒是工作在程式之下的,而我們程式的堆記憶體是程式內的所以執行緒都可以訪問到的區域。這就會造成一個問題,比如,某個執行緒正在辛辛苦苦的處理資料,這時他被老闆叫去開會,回來發現,資料被改動過了。我們把堆記憶體理解成程式通訊的共享記憶體就很容易理解了,大家都可以訪問你這個記憶體地址,我又不知道這是不是有人在用,所以就需要我們鎖的出現。

二、Synchronized的原理

Synchronized關鍵字的作用:

這是一個同步鎖的概念,被Synchronized修飾的程式碼段可防止被多個執行緒同時執行,必須一個執行緒把Synchronized修飾的程式碼段都執行完畢了,其他執行緒才能開始執行這段程式碼。保證了可見性,原子性,有序性

Synchronized的工作原理:

1. 物件被建立在堆記憶體中的,物件在記憶體中的佈局可分成3塊區域:物件頭,例項資料,對齊填充

例項資料:存放類的屬性資料資訊,包括父類的屬性資訊。

對齊填充:這是由於JVM要求物件初始地址是8位元組的整數倍。

物件頭是Synchronized實現鎖的基礎,主要的結構是有Mark Word  和 型別指標

Mark Word 儲存物件的hashcode,鎖資訊或分代年齡等。

型別指標:指向物件的類後設資料,JVM是通過該指標去確定物件是哪個類的例項。

 

2. 每一個鎖都對應一個monitor物件的起始地址。每個物件例項都會有一個monitor,這是與物件一起建立銷燬或者是執行緒獲得物件鎖時自動生成的。當一個monitor被某執行緒持有後,便處於鎖定狀態了。

monitor:可以理解為一個同步工具或者一種同步機制,通常被描述為一個物件。monitor是執行緒私有的資料結構,每一個執行緒都有一個可用的monitor record列表和一個全域性可用列表。每一個鎖住的物件都會和一個monitor關聯,同時monitor有一個owner欄位存放擁有該鎖的執行緒唯一標識,表示該鎖被這個執行緒佔用了。

 

3. Synchronized常被用於修飾程式碼塊或者方法,我們看看這個兩者有什麼區別。

Synchronized修飾程式碼塊:底層是使用了monitorenter 和 monitorexit 指令。 當執行monitorenter指令時,會嘗試獲得monitor物件,成功就把鎖的計數器_count加1,當執行到 monitorexit 指令時,計數器就減1。

這裡雖然沒有貼原始碼,但是讀者看原始碼是可以知道是會出現兩個monitorexit 指令,這可能會讓我們有點困惑,講道理monitorenter 和 monitorexit 指令應該是一同出現的吧,那為什麼會出現兩個monitorexit 指令呢?

其實啊,這是為了保證在方法異常時,monitorenter 和 monitorexit 指令也是可以配對執行,編譯器會自動產生一個異常處理器,目的是用來執行異常的monitorexit 指令去釋放monitor。

Synchronized修飾方法:是用ACC_SYNCHRONIZED標識,JVM會通過該標識去執行上面的同步過程二、Synchronized的優化

三、Synchronized的優化

在JDK1.5之前Synchronized的效率是很低的,這是因為底層的monitorenter 和 monitorexit 指令都是執行在核心態的,而使用者態去到核心態的時間開銷是不低的。以至於在JDK1.5,引入了Synchronized的新概念:無鎖,偏向鎖,輕量級鎖,重量級鎖。這些只是使用Synchronized鎖後的一種鎖狀態儲存的地方就是開頭介紹的Mark Word中。

這裡先不講這四種狀態的介紹,我們留到之後在來開一片JAVA鎖的全家桶。現在我們只需要先知道,鎖的狀態的等級:無鎖<偏向鎖<輕量級鎖<重量級鎖。鎖的狀態只能從低等級的升級到高等級,而不能反向,也就是說升級的方向是:無鎖->偏向鎖->輕量級鎖->重量級鎖。

優化的重點來了,前面搞這麼多的鎖狀態其實就是要優化解決Synchronized頻繁的從使用者態到核心態的轉移。真正會進入到核心態的是升級到重量級鎖時。 這也是現在Synchronized的效能可以和Lock交手的原因。

四、volatile和Synchronized 的區別

volatile關鍵字的作用:

保證可見性和有序性(不保證原子性),如果一個變數被volatile修飾我們稱為共享變數,那一個執行緒修改了這個共享變數後,其他執行緒是立馬可以知道的。因為該變數會強制立刻重新整理到主存

禁止指令重新排序,在volatile變數之前的指令不能在volatile變數之後執行,在volatile變數之後的指令不能在volatile變數之前執行。(使用場景可以看看單例模式的雙重校驗鎖機制)。

volatile和Synchronized 的區別:

(1)volatile只能作用與變數,使用範圍較小。Synchronized 可以用在變數,方法,類,同步程式碼塊等,使用廣

(2)volatile保證可見性和有序性(不保證原子性),而Synchronized都保證。

(3)volatile不會造成執行緒阻塞,Synchronized可能會造成執行緒阻塞

五、Lock和Synchronized 的區別

Lock關鍵字的作用:

Lock和Synchronized差不多,只是一個是有程式設計師手動去釋放鎖(Lock),一個是由JVM去自動釋放鎖(Synchronized)。

Lock和Synchronized 的區別:

(1)用法不同:Synchronized是託管於JVM會自動的釋放鎖,而Lock需要我們顯示的指定頭尾

(2)效能:在JDK1.5之後兩者差不多了,可能還是lock會快一點點吧。

(3)底層不同:Synchronized是悲觀鎖,最終基於作業系統的mutex lock來實現互斥;Lock是樂觀鎖,CAS+volatile。

(4)功能:Lock提供高階功能實現:可定時,可輪詢,可中斷,讀寫鎖,公平鎖或者非公平鎖。而Synchronized只是一個普通的非公平鎖。但是他們兩者都是可重入鎖


總結

程式設計世界博大精深,需要我們細細品味,好好學習,共同進步,加油少年郎。對了,差點忘記怎麼暴打面試官了,當你可以理解這篇文章了,被問到Synchronized時,嘻嘻,就是在送。

相關文章