自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

開(kāi)發(fā) 后端
Synchronized 關(guān)鍵字算是Java的元老級(jí)鎖了,一開(kāi)始它撐起了Java的同步任務(wù),其用法簡(jiǎn)單粗暴容易上手。但是有些與它相關(guān)的知識(shí)點(diǎn)還是需要我們開(kāi)發(fā)者去深入掌握的。

Synchronized 關(guān)鍵字算是Java的元老級(jí)鎖了,一開(kāi)始它撐起了Java的同步任務(wù),其用法簡(jiǎn)單粗暴容易上手。但是有些與它相關(guān)的知識(shí)點(diǎn)還是需要我們開(kāi)發(fā)者去深入掌握的。

比如,我們都知道通過(guò) Synchronized 鎖來(lái)實(shí)現(xiàn)互斥功能,可以用在方法或者代碼塊上,那么不同用法都是怎么實(shí)現(xiàn)的,以及都經(jīng)歷了了哪些優(yōu)化等等問(wèn)題都需要我們?cè)鷮?shí)的理解。

[[338885]]

一、基本用法

通常我們可以把 Synchronized 用在一個(gè)方法或者代碼塊里,方法又有普通方法或者靜態(tài)方法。

對(duì)于普通同步方法,鎖是當(dāng)前實(shí)例對(duì)象,也就是this

  1. public class TestSyn{ 
  2.   private int i=0
  3.   public synchronized void incr(){ 
  4.     i++; 
  5.   } 

對(duì)于靜態(tài)同步方法,鎖是Class對(duì)象

  1. public class TestSyn{ 
  2.   private static int i=0
  3.   public static synchronized void incr(){ 
  4.     i++; 
  5.   } 
  6. }   

對(duì)于同步代碼塊,鎖是同步代碼塊里的對(duì)象

  1. public class TestSyn{ 
  2.   private  int i=0
  3.   Object o = new Object(); 
  4.   public  void incr(){ 
  5.     synchronized(o){ 
  6.         i++; 
  7.     } 
  8.   } 

二、實(shí)現(xiàn)原理

在JVM規(guī)范中介紹了 Synchronized 的實(shí)現(xiàn)原理,JVM基于進(jìn)入和退出Monitor對(duì)象來(lái)實(shí)現(xiàn)方法同步和代碼塊同步,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣。

代碼塊同步是使用monitorenter和monitorexit指令實(shí)現(xiàn)的,而方法同步是使用另外一種方式實(shí)現(xiàn)的,通過(guò)一個(gè)方法標(biāo)志(flag) ACC_SYNCHRONIZED來(lái)實(shí)現(xiàn)的。

1. 同步代碼塊的實(shí)現(xiàn)

(1) monitorenter 和 monitorexit

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter (參考來(lái)源)

下面看下JVM規(guī)范里對(duì)moniterenter 和 monitorexit的介紹:

Each object has a monitor associated with it. The thread that executes monitorenter gains ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread waits until the object is unlocked,

每個(gè)對(duì)象都有一個(gè)監(jiān)視器(Moniter)與它相關(guān)聯(lián),執(zhí)行moniterenter指令的線程將獲得與objectref關(guān)聯(lián)的監(jiān)視器的所有權(quán),如果另一個(gè)線程已經(jīng)擁有與objectref關(guān)聯(lián)的監(jiān)視器,則當(dāng)前線程將等待直到對(duì)象被解鎖為止。

A monitorenter instruction may be used with one or more monitorexit instructions to implement a synchronized statement in the Java programming language. The monitorenter and monitorexit instructions are not used in the implementation of synchronized methods

重點(diǎn)來(lái)了,上面這段介紹了兩點(diǎn):

  • 通過(guò)monitorenter和monitorexit指令來(lái)實(shí)現(xiàn)Java語(yǔ)言的同步代碼塊(后面有代碼示例)
  • monitorenter和monitorexit指令沒(méi)有被用在同步方法上!!!

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

2. 同步方法的實(shí)現(xiàn)

先看下JVM規(guī)范里怎么說(shuō)的:

https://docs.oracle.com/javase/specs/jvms/se6/html/Compiling.doc.html#6530 (參考來(lái)源)

A synchronized method is not normally implemented using monitorenter and monitorexit. Rather, it is simply distinguished in the runtime constant pool by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the current thread acquires a monitor, invokes the method itself, and releases the monitor whether the method invocation completes normally or abruptly.

上面這段話主要講了幾點(diǎn):

  • 同步方法的實(shí)現(xiàn)不是基于monitorenter和monitorexit指令來(lái)實(shí)現(xiàn)的
  • 在運(yùn)行時(shí)常量池里通過(guò)ACC_SYNCHRONIZED來(lái)區(qū)分是否是同步方法,方法執(zhí)行時(shí)會(huì)檢查該標(biāo)志
  • 當(dāng)一個(gè)方法有這個(gè)標(biāo)志的時(shí)候,進(jìn)入的線程首先需要獲得監(jiān)視器才能執(zhí)行該方法
  • 方法結(jié)束或者拋異常時(shí)會(huì)釋放監(jiān)視器
  1. public class TestSyn { 
  2.  
  3.     private int i=0
  4.     // 同步方法 
  5.     public synchronized void incer(){ 
  6.         i++; 
  7.     } 
  8.     // 同步代碼塊 
  9.     public  void decr(){ 
  10.         synchronized (this) { 
  11.             i--; 
  12.         } 
  13.     } 

可以通過(guò)反編譯字節(jié)碼來(lái)查看底層是怎么實(shí)現(xiàn)的

  1. // 得到字節(jié)碼 
  2. javac TestSyn.java 
  1. // 反編譯字節(jié)碼 
  2. javap -v TestSyn.class 

同步代碼塊的反編譯結(jié)果如下:

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

同步方法的反編譯結(jié)果如下:

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

三、鎖升級(jí)

1. Java對(duì)象頭介紹

(1) 對(duì)象的內(nèi)存布局

在我們常見(jiàn)的HotSpot虛擬機(jī)中對(duì)象由三部分組成,分別是對(duì)象頭,實(shí)例數(shù)據(jù),以及對(duì)齊填充位。

其中對(duì)象頭是跟鎖信息相關(guān)的部分,在對(duì)象頭里會(huì)存儲(chǔ)該對(duì)象運(yùn)行時(shí)數(shù)據(jù),包括哈希碼,GC分代年齡,鎖狀態(tài)(無(wú)鎖,偏向鎖,輕量級(jí)鎖,重量級(jí)鎖),是否偏向鎖,偏向線程ID等信息。

存儲(chǔ)上述這些的區(qū)域叫做Mark Word(標(biāo)記詞),除了這部分對(duì)象頭還有一部分區(qū)域用來(lái)存儲(chǔ)類型指針,可以通過(guò)該類型指針來(lái)定位對(duì)象的元數(shù)據(jù)信息。下面重點(diǎn)看下,對(duì)象頭的內(nèi)存布局,因?yàn)檫@部分是跟我們這次相關(guān)的。

對(duì)象在內(nèi)存中的表示如下圖:

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

對(duì)象頭的結(jié)構(gòu)表示如下圖:

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

mark word的表示如下圖:

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

2. 什么是鎖升級(jí)

下面舉個(gè)搶茅坑的例子來(lái)解釋一下鎖升級(jí)過(guò)程。

(1) 當(dāng)只有一個(gè)線程訪問(wèn)時(shí)叫做偏向鎖

假設(shè)我們每個(gè)廁所都有一把鑰匙,要想使用廁所首先必須得獲得鎖。某天上午員工甲急急忙忙的打完卡上廁所了,并在廁所門(mén)上貼了 “工號(hào)007使用中”的標(biāo)簽,說(shuō)明目前被工號(hào)007(相當(dāng)于線程id)的員工占用呢,他再次向進(jìn)入的時(shí)候只要上面的標(biāo)簽還顯示工號(hào)007,他自己可以隨便進(jìn)入,不需要再次上鎖了,有點(diǎn)偏向工號(hào)007員工的意思,所以這叫偏向鎖。

(2) 發(fā)生競(jìng)爭(zhēng)的時(shí)候升級(jí)成輕量級(jí)鎖 (自旋等待)

員工甲正在使用廁所的時(shí)候,又來(lái)了兩個(gè)人想用廁所,但發(fā)現(xiàn)廁所被人使用著呢,無(wú)法獲得鎖。所以只能在外面等著甲出來(lái),他們等的過(guò)程叫做“自旋”,這個(gè)叫做輕量級(jí)鎖。

那么又有一個(gè)問(wèn)題,當(dāng)甲出來(lái)之后正等著的那兩個(gè)人誰(shuí)活得鎖呢?有兩種方式,按到達(dá)的順序來(lái)排隊(duì)或者不排隊(duì),這兩種都可以實(shí)現(xiàn),前者叫做公平鎖,后者叫做非公平鎖。

(3) 自旋等待沒(méi)結(jié)果的時(shí)候升級(jí)成重量級(jí)鎖

但那兩個(gè)人自旋一段時(shí)間之后發(fā)現(xiàn)甲還沒(méi)出來(lái)(JDK1.6規(guī)定為10次),一直這么等也不是個(gè)法子啊,所以打算向上升級(jí),找?guī)芾韱T(操作系統(tǒng))反饋,升級(jí)成了重量級(jí)鎖了。

鎖的狀態(tài)總共有四種,無(wú)鎖狀態(tài)、偏向鎖、輕量級(jí)鎖和重量級(jí)鎖。隨著鎖的競(jìng)爭(zhēng),鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖,再升級(jí)的重量級(jí)鎖。另外關(guān)注公眾號(hào)Java技術(shù)棧回復(fù)JVM46獲取一份46頁(yè)的JVM調(diào)優(yōu)教程。

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

鎖升級(jí)過(guò)程中mark word的變化如下:

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

(4) 偏向鎖

偏向鎖也是JDK 1.6中引入的一項(xiàng)鎖優(yōu)化, 引入它是為了優(yōu)化在沒(méi)有鎖競(jìng)爭(zhēng)場(chǎng)景下的鎖消除。比如一段同步代碼一直是由單個(gè)線程調(diào)用,在這種場(chǎng)景下就沒(méi)必要使用同步鎖了,這里指的同步鎖不是指 synchronized,而是說(shuō)沒(méi)不要到操作系統(tǒng)層面的互斥量了。

偏向鎖的偏向是指該同步代碼會(huì)一直偏向第一個(gè)調(diào)用它的線程,直到有別的線程過(guò)來(lái)競(jìng)爭(zhēng)這把鎖,在第一次調(diào)用同步代碼并獲得鎖時(shí)會(huì)在對(duì)象頭和棧幀鎖記錄行(Lock Record)里存儲(chǔ)偏向線程Id,該線程在此進(jìn)入的時(shí)候就不需要重新申請(qǐng)鎖了。只需檢測(cè)對(duì)象頭的Mark Word里是否存儲(chǔ)著指向該線程的ID即可。

直到又有線程來(lái)競(jìng)爭(zhēng)這把鎖的時(shí)候偏向鎖會(huì)撤銷偏向。

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

Synchronized 天天用,實(shí)現(xiàn)原理你懂嗎?

(5) 輕量級(jí)鎖

輕量級(jí)鎖是JDK 1.6之中加入的新型鎖機(jī)制, 它名字中的“輕量級(jí)”是相對(duì)于使用操作系統(tǒng)。

互斥量來(lái)實(shí)現(xiàn)的傳統(tǒng)鎖而言的, 因此傳統(tǒng)的鎖機(jī)制就稱為“重量級(jí)”鎖。它并不是用來(lái)代替重量級(jí)鎖的, 它的本意是在統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗。

線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。

然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競(jìng)爭(zhēng)鎖,當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖.一直原地自旋,如果自旋數(shù)達(dá)到10次了則升級(jí)為重量級(jí)鎖。

(6) 重量級(jí)鎖

競(jìng)爭(zhēng)的線程自旋一段時(shí)間未能獲取鎖之后會(huì)升級(jí)為重量級(jí)鎖,這個(gè)時(shí)候鎖的獲取與釋放都會(huì)由操作系統(tǒng)來(lái)分配了,如果持有鎖的線程釋放鎖之后操作系統(tǒng)會(huì)喚醒所有阻塞的那些線程,并進(jìn)入新一輪的爭(zhēng)搶模式,需要注意的是這些阻塞的線程沒(méi)有獲得鎖的優(yōu)先級(jí),也就是說(shuō)synchronized鎖是非公平的。

除此之外synchronized對(duì)中斷操作也是無(wú)感的,不會(huì)因?yàn)楸恢袛喽艞壸枞却?,它要么得到鎖要么一直阻塞。

 

責(zé)任編輯:趙寧寧 來(lái)源: 博客園
相關(guān)推薦

2019-09-06 09:11:36

以太網(wǎng)數(shù)據(jù)二層交換

2017-12-06 16:28:48

Synchronize實(shí)現(xiàn)原理

2021-01-08 08:34:09

Synchronize線程開(kāi)發(fā)技術(shù)

2021-07-04 08:01:30

Synchronize線程安全并發(fā)編程

2025-03-20 06:48:55

性能優(yōu)化JDK

2022-12-26 09:27:48

Java底層monitor

2019-11-28 10:45:28

ZooKeeper源碼分布式

2015-05-26 11:10:45

沃爾瑪OpenStack

2019-09-09 09:30:59

Git行程Linux

2019-09-03 09:19:34

CPU架構(gòu)內(nèi)核

2020-12-29 16:55:44

ZooKeeper運(yùn)維數(shù)據(jù)結(jié)構(gòu)

2017-02-27 10:43:07

Javasynchronize

2020-08-13 09:55:37

Stream代碼Java

2021-01-11 15:02:27

Redis數(shù)據(jù)庫(kù)命令

2010-08-29 21:09:57

DHCP協(xié)議

2024-03-07 07:47:04

代碼塊Monitor

2024-03-15 15:12:27

關(guān)鍵字底層代碼

2022-07-18 07:12:33

開(kāi)源Linux

2020-09-16 06:09:43

開(kāi)源工具PulpLinux

2023-05-10 08:29:28

Spring配置原理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)