Java中Synchronized鎖的使用和原理詳解
Synchronized 鎖的使用和原理
Synchronized是java
的一個關鍵字,加鎖方式有: 對象鎖、類鎖
其用法有:
- 普通同步方法,鎖是當前實例對象
- 靜態(tài)同步方法,鎖是當前類的class對象
- 同步方法塊,鎖是括號里面的對象
Synchronized的使用
對象鎖
方法鎖: 默認所對象為this
,當前實例對象
public class SynchronizedMethodLock implements Runnable{ static SynchronizedMethodLock instence = new SynchronizedMethodLock(); @Override public void run() { method(); } public synchronized void method() { System.out.println("我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "結束"); } public static void main(String[] args) { Thread thread1 = new Thread(instence); Thread thread2 = new Thread(instence); thread1.start(); thread2.start(); } }
同步代碼塊鎖: 手動指定鎖定對象(this
或者自定義鎖)
- this
public class SynchronizedThisLock implements Runnable{ static SynchronizedThisLock instence = new SynchronizedThisLock(); @Override public void run() { // 同步代碼塊形式——鎖為this,兩個線程使用的鎖是一樣的,線程1必須要等到線程0釋放了該鎖后,才能執(zhí)行 synchronized (this) { System.out.println("我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "結束"); } } public static void main(String[] args) { Thread t1 = new Thread(instence); Thread t2 = new Thread(instence); t1.start(); t2.start(); } }
- 自定義對象
public class SynchronizedObjectLock implements Runnable{ static SynchronizedObjectLock instence = new SynchronizedObjectLock(); // 創(chuàng)建2把鎖 Object block1 = new Object(); Object block2 = new Object(); @Override public void run() { // 這個代碼塊使用的是第一把鎖,當他釋放后,后面的代碼塊由于使用的是第二把鎖,因此可以馬上執(zhí)行 synchronized (block1) { System.out.println("block1鎖,我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("block1鎖,"+Thread.currentThread().getName() + "結束"); } synchronized (block2) { System.out.println("block2鎖,我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("block2鎖,"+Thread.currentThread().getName() + "結束"); } } public static void main(String[] args) { Thread t1 = new Thread(instence); Thread t2 = new Thread(instence); t1.start(); t2.start(); } }
類鎖
synchronized
修飾靜態(tài)方法或指定鎖對象為Class對象
public class SynchronizedClassLock implements Runnable{ static SynchronizedObjectLock instence1 = new SynchronizedObjectLock(); static SynchronizedObjectLock instence2 = new SynchronizedObjectLock(); @Override public void run() { // 所有線程需要的鎖都是同一把 synchronized(SynchronizedClassLock.class){ System.out.println("我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "結束"); } } public static void main(String[] args) { Thread t1 = new Thread(instence1); Thread t2 = new Thread(instence2); t1.start(); t2.start(); } }
public class SynchronizedStaticMethodLock implements Runnable{ static SynchronizedStaticMethodLock instence1 = new SynchronizedStaticMethodLock(); static SynchronizedStaticMethodLock instence2 = new SynchronizedStaticMethodLock(); @Override public void run() { method(); } public static synchronized void method() { // 所有線程需要的鎖都是同一把 System.out.println("我是線程" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "結束"); } public static void main(String[] args) { Thread t1 = new Thread(instence1); Thread t2 = new Thread(instence2); t1.start(); t2.start(); } }
synchronized原理
synchronized
枷鎖和釋放鎖是基于monitorenter
和monitorexit
指令實現(xiàn)的
Monitorenter
和Monitorexit
指令,會讓對象在執(zhí)行,使其鎖計數(shù)器加1或者減1。每一個對象在同一時間只與一個monitor(鎖)相關聯(lián),而一個monitor在同一時間只能被一個線程獲得,一個對象在嘗試獲得與這個對象相關聯(lián)的Monitor鎖的所有權的時候,
monitorenter指令
- monitor計數(shù)器為0,意味著目前還沒有被獲得,那這個線程就會立刻獲得然后把鎖計數(shù)器+1,一旦+1,別的線程再想獲取,就需要等待
- 如果這個monitor已經拿到了這個鎖的所有權,又重入了這把鎖,那鎖計數(shù)器就會累加,變成2,并且隨著重入的次數(shù),會一直累加
- 這把鎖已經被別的線程獲取了,等待鎖釋放
monitorexit指令
:釋放對于monitor的所有權,釋放過程很簡單,就是講monitor的計數(shù)器減1,如果減完以后,計數(shù)器不是0,則代表剛才是重入進來的,當前線程還繼續(xù)持有這把鎖的所有權,如果計數(shù)器變成0,則代表當前線程不再擁有該monitor的所有權,即釋放鎖。
JVM中鎖的優(yōu)化
簡單來說在JVM中monitorenter
和monitorexit
字節(jié)碼依賴于底層的操作系統(tǒng)的Mutex Lock
來實現(xiàn)的,但是由于使用Mutex Lock
需要將當前線程掛起并從用戶態(tài)切換到內核態(tài)來執(zhí)行,這種切換的代價是非常昂貴的;然而在現(xiàn)實中的大部分情況下,同步方法是運行在單線程環(huán)境(無鎖競爭環(huán)境)如果每次都調用Mutex Lock
那么將嚴重的影響程序的性能。不過在jdk1.6中對鎖的實現(xiàn)引入了大量的優(yōu)化,如鎖粗化(Lock Coarsening)、鎖消除(Lock Elimination)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)、適應性自旋(Adaptive Spinning)等技術來減少鎖操作的開銷。
鎖粗化(Lock Coarsening)
:也就是減少不必要的緊連在一起的unlock,lock操作,將多個連續(xù)的鎖擴展成一個范圍更大的鎖。鎖消除(Lock Elimination)
:通過運行時JIT編譯器的逃逸分析來消除一些沒有在當前同步塊以外被其他線程共享的數(shù)據(jù)的鎖保護,通過逃逸分析也可以在線程本的Stack上進行對象空間的分配(同時還可以減少Heap上的垃圾收集開銷)。輕量級鎖(Lightweight Locking)
:這種鎖實現(xiàn)的背后基于這樣一種假設,即在真實的情況下我們程序中的大部分同步代碼一般都處于無鎖競爭狀態(tài)(即單線程執(zhí)行環(huán)境),在無鎖競爭的情況下完全可以避免調用操作系統(tǒng)層面的重量級互斥鎖,取而代之的是在monitorenter和monitorexit中只需要依靠一條CAS原子指令就可以完成鎖的獲取及釋放。當存在鎖競爭的情況下,執(zhí)行CAS指令失敗的線程將調用操作系統(tǒng)互斥鎖進入到阻塞狀態(tài),當鎖被釋放的時候被喚醒(具體處理步驟下面詳細討論)。偏向鎖(Biased Locking)
:是為了在無鎖競爭的情況下避免在鎖獲取過程中執(zhí)行不必要的CAS原子指令,因為CAS原子指令雖然相對于重量級鎖來說開銷比較小但還是存在非??捎^的本地延遲。適應性自旋(Adaptive Spinning)
:當線程在獲取輕量級鎖的過程中執(zhí)行CAS操作失敗時,在進入與monitor相關聯(lián)的操作系統(tǒng)重量級鎖(mutex semaphore)前會進入忙等待(Spinning)然后再次嘗試,當嘗試一定的次數(shù)后如果仍然沒有成功則調用與該monitor關聯(lián)的semaphore(即互斥鎖)進入到阻塞狀態(tài)。樂觀鎖
:是一種樂觀思想,即認為讀多寫少,遇到并發(fā)寫的可能性低,每次去拿數(shù)據(jù)的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),采取在寫時先讀出當前版本號,然后加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重復讀-比較-寫的操作。java中的樂觀鎖基本都是通過CAS操作實現(xiàn)的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。悲觀鎖
:就是悲觀思想,即認為寫多,遇到并發(fā)寫的可能性高,每次去拿數(shù)據(jù)的時候都認為別人會修改,所以每次在讀寫數(shù)據(jù)的時候都會上鎖,這樣別人想讀寫這個數(shù)據(jù)就會block直到拿到鎖。java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如RetreenLock。
synchronized鎖升級過程
鎖的級別從低到高逐步升級
無鎖->偏向鎖->輕量級鎖->重量級鎖
到此這篇關于Java中Synchronized鎖的使用和原理詳解的文章就介紹到這了,更多相關Java中Synchronized鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Mybatis返回值(resultType&resultMap)的具體使用
返回值屬性有兩種設置,一種是resultType,一種是resultMap,本文主要介紹了Mybatis返回值(resultType&resultMap)的具體使用,具有一定的參考價值,感興趣的可以了解一下2023-08-08JVM(Java虛擬機)簡介(動力節(jié)點Java學院整理)
Java虛擬機(Jvm)是可運行Java代碼的假想計算機。Java虛擬機包括一套字節(jié)碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。對java jvm 虛擬機感興趣的朋友通過本文一起學習吧2017-04-04mybatis?對于生成的sql語句?自動加上單引號的情況詳解
這篇文章主要介紹了mybatis?對于生成的sql語句?自動加上單引號的情況詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01Java使用Runnable接口創(chuàng)建線程的示例代碼
在Java中,多線程編程是實現(xiàn)并發(fā)操作的重要手段之一,通過多線程,程序可以同時執(zhí)行多個任務,從而提高應用程序的效率和響應速度,Java提供了多種創(chuàng)建線程的方式,其中實現(xiàn)Runnable接口是最常見且推薦的方式之一,本文將詳細介紹如何使用Runnable接口創(chuàng)建線程2025-02-02