synchronized實現(xiàn)原理

前言:

synchronized的簡介

Synchronized的用法就是為了避免因資源搶占導(dǎo)致的數(shù)據(jù)錯亂夜矗,從而讓線程進(jìn)行同步筝蚕,保證同步內(nèi)的對象只有一個線程來執(zhí)行值更改使用操作,是并發(fā)控制中必不可少的部分迎膜,懂的都懂书斜,今天來深扒一下它

synchronized的特性

原子性伦连、指一個操作或多個操作,要么全部執(zhí)行并且執(zhí)行的過程并不會被任何因素打斷袭异,要么就都不執(zhí)行钠龙,比如

int i=1;這個操作就是原子性要么執(zhí)行要么不執(zhí)行御铃,而i++就不是原子性的碴里,因為包含了讀取、計算上真、賦值幾步咬腋,原值可能還沒完成時就已經(jīng)被賦值了,而原子性保證了執(zhí)行過程不會被中斷

synchronized與volatile的最大區(qū)別就是原子性谷羞,volatile不具備原子性

可見性帝火、指多個線程訪問一個資源時,該資源的狀態(tài)湃缎、值信息等對于其他線程都是可見的犀填,比如

int i =1;這個值如果是全局變量嗓违,在synchronized內(nèi)執(zhí)行變化它時九巡,其他線程也可以讀取到當(dāng)前i的值

有序性、程序執(zhí)行的順序都是按照代碼先后順序執(zhí)行蹂季,由于Java允許編譯器和處理器對指令進(jìn)行重排冕广,但他并不影響單線程的順序,而是影響了多線程的并發(fā)執(zhí)行順序性偿洁,synchronized則保證了同步代碼塊內(nèi)是有順序的

可重入性撒汉、就是擁有了這個鎖還能重復(fù)申請

一、synchronized的使用

1.修飾實例方法

鎖的是當(dāng)前實例對象涕滋,進(jìn)入同步代碼獲得當(dāng)前實例

public synchronized void add(){...};

反編譯時睬辐,add方法的flags多了一個ACC_SYNCHRONIZED標(biāo)志,這標(biāo)志用來告訴JVM這是一個同步方法,在進(jìn)入該方法之前先獲取相應(yīng)的鎖溯饵,鎖的計數(shù)器加1侵俗,方法結(jié)束后計數(shù)器-1,如果獲取失敗就阻塞住丰刊,直到該鎖被釋放隘谣。

2.修飾靜態(tài)方法

鎖的是當(dāng)前類,進(jìn)入同步代碼前獲取當(dāng)前類對象

public static synchronized void add(){...};

3.同步代碼塊

鎖括號內(nèi)的對象或當(dāng)前類

public class Test{

? private Test instance = new Test();

? synchronized(instance);? //鎖對象

? synchronized(Test.class){...} //鎖類

}

反編譯時啄巧,同步代碼塊是由monitorenter指令進(jìn)入寻歧,然后monitorexit釋放鎖

二、為什么任何對象都可以作為鎖

詳見:JVM內(nèi)存模型中對象的組成結(jié)構(gòu)

在JVM 中每個對象分為三部分存在:對象頭棵帽、示例數(shù)據(jù)熄求、對齊填充

對象頭中又有MarkWorld(運(yùn)行時元數(shù)據(jù))

鎖狀態(tài)標(biāo)志中便記錄了加鎖的信息

Mark Word在不同的鎖狀態(tài)下存儲的內(nèi)容不同,在32位JVM中是這么存的:

偏向鎖逗概、輕量級鎖弟晚、重量級鎖的具體使用會在下面具體介紹區(qū)別和聯(lián)系

而查看對象頭信息

總結(jié)

由于每個對象的Mark Word中都有儲存鎖的信息,可以說 鎖是對象逾苫,任何對象都可以作為鎖

偏向鎖卿城、輕量級鎖、重量級鎖

偏向鎖

只有第一個申請鎖的線程會使用鎖铅搓,有其他線程競爭就膨脹為輕量級鎖

當(dāng)一個線程訪問同步塊并獲取鎖時瑟押,會在對象頭和棧幀中的所記錄里存儲鎖偏向的線程ID,以后該線程進(jìn)入和退出同步塊不需要CAS(Compare and Swap星掰,比較并替換)操作來爭奪鎖資源多望。對一個線程的偏向,如果有其他線程競爭才去釋放(再替換線程ID即可)氢烘,當(dāng)新線程發(fā)起替換對象頭中的線程ID為自身的CAS請求時怀偷,回去判斷擁有此偏向鎖的線程是否還活著,如果不活著播玖,則置位無鎖狀態(tài)椎工,如果或者則掛起線程,并將只想當(dāng)前線程的鎖記錄地址放入頭對象蜀踏,膨脹成輕量級鎖维蒙,然后恢復(fù)持有鎖的線程

ps:當(dāng)前線程掛起再恢復(fù)的過程中并沒有發(fā)聲鎖的轉(zhuǎn)移,只是“將頭對象中的線程ID變更為只想鎖記錄地址的指針”果覆,偏向鎖是在單線程執(zhí)行代碼塊時使用的機(jī)制颅痊,如果多線程并發(fā)時(線程A并未執(zhí)行完同步代碼塊,B線程發(fā)起了鎖的申請)局待,則一定會轉(zhuǎn)化為輕量級鎖或重量級鎖斑响。

輕量級鎖

一個線程自旋等待持有鎖線程釋放鎖吗讶,若自旋后還沒獲得膨脹為重量級鎖

每次線程想進(jìn)入同步代碼塊的時候,都得通過CAS嘗試將對象頭中的所指針替換為自身棧中的記錄恋捆,如果沒有成功,則進(jìn)入而自適應(yīng)的自旋(動態(tài)改變自旋等待次數(shù)重绷,有另外一個線程來競爭鎖時沸停,線程在原地循環(huán)等待而不是阻塞,獲得鎖的線程釋放后就立馬獲得鎖)昭卓,如果自適應(yīng)自旋轉(zhuǎn)時還沒有獲得鎖愤钾,則膨脹為重量鎖

重量級鎖

實際的多線程阻塞等待,切換鎖的過程

通過對象內(nèi)部的監(jiān)視器(monitor)實現(xiàn)候醒,其中monitor的本質(zhì)是依賴于底層操作系統(tǒng)的Mutex Lock實現(xiàn)能颁,操作系統(tǒng)實現(xiàn)線程之間的切換需要從用戶態(tài)到內(nèi)核態(tài)的切換,切換成本非常高倒淫。

應(yīng)用場景

偏向鎖:無實際競爭伙菊,且將來只有第一個申請鎖的線程會使用鎖

輕量級鎖:無實際競爭,多個線程交替使用鎖敌土;允許短時間的所競爭

重量級鎖:有實際競爭镜硕,且所競爭時間長

三、synchronized的底層實現(xiàn)

每個對象都有一個監(jiān)視器鎖monitor 當(dāng)monitor被占用時就會處于鎖定狀態(tài)返干,執(zhí)行monitorenter指令時兴枯,嘗試獲取monitor的所有權(quán),過程如下:

前提:

ObjectMonitor中有兩個隊列矩欠,_WaitSet 和 _EntryList财剖,用來保存ObjectWaiter對象列表( 每個等待鎖的線程都會被封裝成ObjectWaiter對象),_owner指向持有ObjectMonitor對象的線程

ObjectMonitor成員變量

? // initialize the monitor, exception the semaphore, all other fields

? // are simple integers or pointers

? ObjectMonitor() {

? ? _header? ? ? = NULL;

? ? _count? ? ? ? = 0;=============》計數(shù)器

? ? _waiters? ? ? = 0,

? ? _recursions? = 0;

? ? _object? ? ? = NULL;

? ? _owner? ? ? ? = NULL;===========》所持有該對象的線程

? ? _WaitSet? ? ? = NULL;==============》等待線程集合

? ? _WaitSetLock? = 0 ;========》保護(hù)等待隊列簡單的自旋鎖

? ? _Responsible? = NULL ;

? ? _succ? ? ? ? = NULL ;

? ? _cxq? ? ? ? ? = NULL ;====》阻塞上entry上最近科大的縣城列表癌淮,該列表是由waitNode構(gòu)成躺坟,扮演者線程代理

? ? FreeNext? ? ? = NULL ;

? ? _EntryList? ? = NULL ;============》入口線程集合

? ? _SpinFreq? ? = 0 ;

? ? _SpinClock? ? = 0 ;

? ? OwnerIsThread = 0 ;

? ? _previous_owner_tid = 0;

? }

ObjectMonitor工作過程

1. 當(dāng)多一個線程訪問同一段同步代碼塊時,進(jìn)入_EntryList集合

2.當(dāng)線程獲取到對象的monitor后進(jìn)入_Owner區(qū)域该默,并把monitor中的owner變量設(shè)為當(dāng)前線程瞳氓,monitor是依賴于底層操作系統(tǒng)的mutex lock來實現(xiàn)互斥的,線程獲取mutex成功栓袖,則會持有該mutex這時候其他線程無法獲取該metux匣摘、并將monitor的count+1(此時表示當(dāng)前線程持有當(dāng)前對象并加鎖)

3.當(dāng)線程調(diào)用wait()方法后,釋放當(dāng)前持有的monitor既釋放所持有的mutex裹刮,owner變量恢復(fù)為null音榜,count-1,同時進(jìn)入_WaitSet集合等待調(diào)用notify/notifyAll被喚醒

4.若當(dāng)前線程執(zhí)行完畢捧弃,釋放當(dāng)前持有的monitor赠叼,owner變量恢復(fù)為null

總結(jié):同步鎖在這種實現(xiàn)方式中擦囊,因為Monitor是依賴于底層的操作系統(tǒng)實現(xiàn),這樣就存在用戶態(tài)和內(nèi)核態(tài)之間的切換嘴办,所以會增加性能的開銷


詳細(xì)流程圖

詳細(xì)代碼

頭文件:

http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/objectMonitor.hpp

實現(xiàn):

http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/objectMonitor.cpp

代碼太長 詳細(xì)解析可看具體代碼解析

四瞬场、其他相關(guān)線程同步相關(guān)

Volatile實現(xiàn)原理

Volatile只能修飾變量,不能修飾方法或代碼塊

Volatile變量的可見性

Java虛擬機(jī)中定義了一種Java內(nèi)存模型(Java Memory Model涧郊,即JMM)來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異贯被,以實現(xiàn)讓Java程序在各種平臺下都能達(dá)到一致的并發(fā)效果,Java的內(nèi)存模型目標(biāo):定義程序中各個變量的訪問規(guī)則妆艘,既在虛擬機(jī)中將變量村存儲到內(nèi)存和從內(nèi)從中去出變量這樣的細(xì)節(jié)

而對于普通變量彤灶,線程A修改值后此時該值在此線程的工作內(nèi)存,尚未同步到主內(nèi)存時批旺,若常出現(xiàn)B線程使用此變量幌陕,此時拿到的是主內(nèi)存修改前的值,便發(fā)生了可見性不一致的問題

volatile可見性的實現(xiàn)就是借助了CPU的lock指令汽煮,通過在寫volatile的機(jī)器指令前加上lock前綴搏熄,使寫volatile具有以下兩個原則:

當(dāng)Volatile變量執(zhí)行寫操作后,JMM會把工作內(nèi)存中的最新變量值強(qiáng)制刷新到主內(nèi)存中

寫操作會導(dǎo)致其他線程中的緩存無效

這樣逗物,其他線程使用緩存時搬卒,發(fā)現(xiàn)本地工作內(nèi)存此變量無效,就會從主內(nèi)存獲取翎卓,這樣獲取到的就是最新的值契邀,實現(xiàn)了線程的可見性

Volatile變量的有序性

volatile是通過編譯器在生成字節(jié)碼時,在指令序列中添加“內(nèi)存屏障”來禁止指令重排序等

JVM的實現(xiàn)會在volatile讀寫前后均加上內(nèi)存屏障失暴,在一定程度上保證有序性坯门。如下所示:

LoadLoadBarrier

volatile 讀操作

LoadStoreBarrier

StoreStoreBarrier

volatile 寫操作

StoreLoadBarrier

Volatile的使用

public class TestVolatile {

? ? public static volatile int counter = 1;

? ? public static void main(String[] args){

? ? ? ? counter = 2;

? ? ? ? System.out.println(counter);

? ? }

}

字節(jié)碼層面

volatile在字節(jié)碼層面,就是使用訪問標(biāo)志:ACC_VOLATILE來表示

Volatile與synchronized區(qū)別

1.volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器(工作內(nèi)存)中的值是不確定的逗扒,需要從主存中讀裙糯鳌;synchronized則是鎖定當(dāng)前變量矩肩,只有當(dāng)前線程可以訪問該變量现恼,其他線程被阻塞住黍檩;

2.volatile僅能使用在變量級別叉袍;synchronized則可以使用在變量、方法刽酱、和類級別的喳逛;

3.volatile僅能實現(xiàn)變量的修改可見性,不能保證原子性棵里;而synchronized則可以保證變量的修改可見性和原子性润文;

4.volatile不會造成線程的阻塞姐呐;synchronized可能會造成線程的阻塞;

Lock原理

Lock的使用

由于Lock是一個Java接口典蝌,所以需要new它的實現(xiàn)類常用的ReentrantLock

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

private Lock lock = new ReentrantLock();

lock.lock();//獲取鎖

//do something

lock.unlock()//釋放鎖

lock.trylock()//獲取鎖 如果鎖被占有就放開

而lock的核心類是(AQS)AbstractQueuedSynchronizer 自旋鎖

synchronized與Lock的區(qū)別

五曙砂、各種名稱鎖總結(jié)

詳細(xì)介紹:https://tech.meituan.com/2018/11/15/java-lock.html

互斥鎖/讀寫鎖

互斥鎖在Java中的具體實現(xiàn)就是ReentrantLock。

讀寫鎖在Java中的具體實現(xiàn)就是ReadWriteLock骏掀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末麦轰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子砖织,更是在濱河造成了極大的恐慌,老刑警劉巖末荐,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侧纯,死亡現(xiàn)場離奇詭異,居然都是意外死亡甲脏,警方通過查閱死者的電腦和手機(jī)眶熬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來块请,“玉大人娜氏,你說我怎么就攤上這事《招拢” “怎么了贸弥?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長海渊。 經(jīng)常有香客問我绵疲,道長,這世上最難降的妖魔是什么臣疑? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任盔憨,我火速辦了婚禮,結(jié)果婚禮上讯沈,老公的妹妹穿的比我還像新娘郁岩。我一直安慰自己,他們只是感情好缺狠,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布问慎。 她就那樣靜靜地躺著,像睡著了一般儒老。 火紅的嫁衣襯著肌膚如雪蝴乔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天驮樊,我揣著相機(jī)與錄音薇正,去河邊找鬼片酝。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的知允。 我是一名探鬼主播贴捡,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼审轮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辽俗,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤疾渣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后崖飘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榴捡,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年朱浴,在試婚紗的時候發(fā)現(xiàn)自己被綠了吊圾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡翰蠢,死狀恐怖项乒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梁沧,我是刑警寧澤檀何,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站廷支,受9級特大地震影響埃碱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜酥泞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一砚殿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芝囤,春花似錦似炎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悯许,卻和暖如春仆嗦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背先壕。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工瘩扼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谆甜,地道東北人。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓集绰,卻偏偏與公主長得像规辱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子栽燕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內(nèi)容