synchronized關(guān)鍵字詳解

前言:如何處理共享數(shù)據(jù)的安全問題?

讓每一個(gè)線程依次的去讀取這個(gè)共享數(shù)據(jù)嫡秕,這樣就不會有任何的數(shù)據(jù)安全問題了丸凭,因?yàn)槊看蚊總€(gè)線程所操作的都是最新的數(shù)據(jù),不會出現(xiàn)臟讀的現(xiàn)象茵瀑。synchronized關(guān)鍵字就是使每個(gè)線程依次排隊(duì)操作共享變量间驮,也就是用來處理共享數(shù)據(jù)的安全性問題。不過這種同步機(jī)制的效率很低马昨。

一竞帽、使用范圍

在Java代碼中,synchronized關(guān)鍵字可以用在代碼塊和方法中:

方法:
    1.實(shí)例方法:被鎖的是該類的實(shí)例對象 
        public synchronized void method() {}
    2.靜態(tài)方法:被鎖的是類對象
        public static synchronized void method() {}
代碼塊:
    1.實(shí)例對象:被鎖的是類的實(shí)例對象
        synchronized (this) {}
    2.class對象:被鎖的是類對象
        synchronized (Synchroinzed.class) {}
    3.任意實(shí)例對象Object :被鎖的是配置的實(shí)例對象
        String lock = "1111";
        synchronized (lock) {}

如果鎖的是類對象的話鸿捧,不管new多少個(gè)實(shí)例對象屹篓,他們都會被鎖住,即線程之間保證同步關(guān)系匙奴。其實(shí)無論對一個(gè)對象進(jìn)行加鎖還是對一個(gè)方法進(jìn)行加鎖堆巧,實(shí)際上都是對對象進(jìn)行加鎖。被加了鎖的對象就叫鎖對象泼菌,在Java中任何一個(gè)對象都能成為鎖對象谍肤。

二、synchronized的執(zhí)行原理

我們給代碼塊添加了synchronized關(guān)鍵字哗伯,查看字節(jié)碼文件谣沸,發(fā)現(xiàn)執(zhí)行同步代碼快就要先執(zhí)行monitorenter指令,退出的時(shí)候執(zhí)行monitorexit指令笋颤。

public class SynchronizedDemo {

    public static void main(String[] args) {
        synchronized (SynchronizedDemo.class) {}
        method();
    }
    private static void method() {}
}

查看class字節(jié)碼:

也就是說使用synchronized進(jìn)行同步乳附,關(guān)鍵就是必須要獲取到對象的監(jiān)視器monitor内地,只有線程獲取到monitor才會繼續(xù)往下執(zhí)行,否則就只能等待赋除。且這個(gè)過程是互斥的阱缓,同一時(shí)刻只能由一個(gè)線程能獲取到monitor

任意一個(gè)對象都擁有自己的監(jiān)視器举农,當(dāng)這個(gè)對象由同步塊或者這個(gè)對象的同步方法調(diào)用時(shí)荆针,執(zhí)行方法的線程必須先獲取該對象的監(jiān)視器才能進(jìn)入同步塊和同步方法。如果沒有獲取到監(jiān)視器的線程將會被阻塞在同步塊和同步方法的入口處颁糟,進(jìn)入到BLOCKED(阻塞)狀態(tài)航背。

這樣我們可以得到:任意線程對一對象的訪問,首先要獲得該對象的監(jiān)視器棱貌,如果獲取失敗玖媚,該線程狀態(tài)變?yōu)锽LOCKED,進(jìn)入阻塞隊(duì)列婚脱,當(dāng)該對象的監(jiān)視器占有者釋放后今魔,在阻塞隊(duì)列中的線程就會有機(jī)會重新獲取該監(jiān)視器。

為什么只有一次monitorenter呢障贸?
答:因?yàn)?strong>synchronized具有重入性错森,即同一個(gè)鎖程中,線程不需要再次獲取同一把鎖篮洁。

三涩维、synchronized與JMM中的三大特性

synchronized具有原子性,可見性袁波,有序性瓦阐,具體內(nèi)容可以查看這篇文章

四锋叨、synchronized的優(yōu)化

前面說了synchronized是保證同一時(shí)刻只有一個(gè)線程能夠獲得對象的監(jiān)視器(monitor),從而進(jìn)入到同步代碼快或者同步方法中宛篇,即表現(xiàn)為互斥性娃磺。那這種方式的效率肯定低下,每次只能過一個(gè)線程叫倍。我們得整點(diǎn)優(yōu)化來縮短獲取鎖的時(shí)間偷卧,這樣就算挨著執(zhí)行,但是每次執(zhí)行的速度很快吆倦。

先說說鎖的四種狀態(tài)听诸,級別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)蚕泽、輕量級鎖狀態(tài)和重量級鎖狀態(tài)晌梨。 這幾個(gè)狀態(tài)會隨著競爭情況而逐漸升級桥嗤,鎖可以升級但不能降級。這種升級不降級的策略目的是為了提高獲得鎖和釋放鎖的效率仔蝌。

1.偏向鎖

大多數(shù)情況下泛领,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得敛惊。使用偏向鎖就是減少無競爭且只有一個(gè)線程使用鎖的情況下使用輕量級鎖產(chǎn)生的性能消耗渊鞋。

當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí),會在對象頭和自己的棧幀中的鎖記錄里存儲偏向線程ID瞧挤,以后該線程在進(jìn)入和退出同步塊時(shí)就不需要進(jìn)行CAS操作來加鎖和解鎖了锡宋,只需要測試一下對象頭的Mark Word中是否存儲指向當(dāng)前線程的偏向鎖。

如果測試成功就表示線程已經(jīng)獲得了鎖特恬。如果測試失敗則還需要測試一下偏向鎖的標(biāo)識是否為1(標(biāo)識當(dāng)前是偏向鎖):如果沒有獲得偏向鎖执俩,使用CAS競爭鎖;如果獲得了偏向鎖鸵鸥,使用CAS將對象頭的偏向鎖指向當(dāng)前線程奠滑。這也就是說偏向鎖只有在初始化的時(shí)候進(jìn)行一次CAS操作即可。

偏向鎖使用了一種等到競爭才釋放鎖的機(jī)制妒穴,即當(dāng)其他線程嘗試競爭偏向鎖時(shí)宋税,持有偏向鎖的線程才會釋放鎖,線程不會主動(dòng)釋放偏向鎖讼油〗苋可如果有兩個(gè)線程進(jìn)行競爭的時(shí)候,偏向鎖就失效了升級稱為輕量級鎖了矮台。這種升級稱為鎖膨脹乏屯。


偏向鎖的撤銷:需要等待全局安全點(diǎn)(此時(shí)沒有正在執(zhí)行的字節(jié)碼),暫停擁有偏向鎖的線程瘦赫,然后檢查持有偏向鎖的線程是否還活著辰晕,如果沒活著,則將對象頭設(shè)置為無鎖确虱。如果線程活著含友,擁有偏向鎖的棧會被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄校辩,棧中的鎖記錄和對象頭的Mark Word要么重新偏向其他線程窘问,要么恢復(fù)到無鎖狀態(tài)或者標(biāo)記該對象不適合作為偏向鎖,最后喚醒暫停的線程宜咒。

如果某些同步代碼塊大多數(shù)情況下都是由兩個(gè)或以上的線程競爭的話惠赫,偏向鎖就是個(gè)累贅了,對于這種情況故黑,我們一開始關(guān)閉即可儿咱。

通過JVM參數(shù)關(guān)閉偏向鎖:-XX:-UseBiasedLocking=false庭砍,那么程序默認(rèn)會進(jìn)入輕量級鎖狀態(tài)。

2.輕量級鎖

輕量級鎖是一種非阻塞同步的樂觀鎖概疆,因?yàn)檫@個(gè)過程并沒有掛起阻塞線程逗威,而是讓線程空循環(huán)等待,串行執(zhí)行岔冀。

輕量級鎖由偏向鎖膨脹而來:

  1. 線程在自己的棧幀中創(chuàng)建鎖記錄LockRecord(開辟位置)
  2. 將鎖對象的對象頭的MarkWord復(fù)制到線程剛剛創(chuàng)建的鎖記錄中(復(fù)制鎖信息)
  3. 將鎖記錄中的Owner指針指向鎖對象(讓線程指向鎖)
  4. 將鎖對象的對象頭的MarkWord替換為指向鎖記錄的指針(讓鎖指向線程)
  5. 鎖對象對象頭的Mark Word的鎖標(biāo)志位變成00凯旭,即表示輕量級鎖

一般輕量級鎖有自旋鎖和自適應(yīng)自旋鎖兩種:

1.自旋鎖

如果線程1持有鎖,而線程2來競爭的時(shí)候使套,線程2會在原地自旋罐呼,而不是阻塞。也就是說獲得鎖的線程1釋放鎖侦高,那這個(gè)線程2立馬就能獲得鎖嫉柴。線程在原地循環(huán)等待是會消耗cpu的,就相當(dāng)于執(zhí)行一個(gè)啥都沒有的for循環(huán)奉呛。實(shí)驗(yàn)表明计螺,大部分的同步代碼快執(zhí)行時(shí)間都很短,所以才有了自旋鎖瞧壮。

根據(jù)以上不難得出自旋鎖的問題:

  1. 如果同步代碼快執(zhí)行的很慢登馒,需要消耗大量的時(shí)間,此時(shí)在原地自旋等待的其他線程就十分耗cpu咆槽。
  2. 本來把一個(gè)線程的鎖釋放后陈轿,當(dāng)前線程是能夠獲得鎖的,可如果好幾個(gè)線程都在競爭秦忿,這就會導(dǎo)致一些線程獲取不到鎖麦射,還在原地循環(huán)等待消耗cpu,甚至一直獲取不到鎖灯谣。

基于問題2潜秋,我們可以給線程空循環(huán)設(shè)置一個(gè)次數(shù),如果線程循環(huán)超過這個(gè)次數(shù)的話使用自旋鎖就不合適了胎许,此時(shí)進(jìn)行鎖膨脹峻呛,將鎖升級為重量級鎖。默認(rèn)情況是10呐萨,可以通過-XX:PreBlockSpin修改杀饵。

2.自適應(yīng)自旋鎖

自旋鎖的線程空循環(huán)等待的自旋次數(shù)并非固定的莽囤,而是動(dòng)態(tài)的根據(jù)實(shí)際情況來改變谬擦,這就是自適應(yīng)自旋鎖。即線程1剛獲得了一個(gè)鎖朽缎,當(dāng)它釋放鎖的后惨远,線程2獲得鎖谜悟。在線程2運(yùn)行的過程中,線程1又想獲得鎖了北秽,不過線程2沒有釋放鎖葡幸,線程1就自旋等待。JVM認(rèn)為贺氓,由于線程1剛剛獲得過該鎖蔚叨,那么線程1這次自旋也是很有可能能夠再次成功的獲得該鎖,所以會適當(dāng)?shù)难娱L線程1的自旋次數(shù)辙培。對應(yīng)的蔑水,如果對于某一個(gè)鎖,一個(gè)線程自旋后很少有機(jī)會獲得該鎖扬蕊,那么以后該線程要獲取該鎖時(shí)直接忽略掉自旋過程搀别,直接升級為重量級鎖,以免長時(shí)間自旋造成資源浪費(fèi)尾抑。

3.重量級鎖

輕量級鎖膨脹后成為重量級鎖歇父,依賴對象內(nèi)部的monitor鎖來實(shí)現(xiàn)。在jdk1.6之前監(jiān)視器鎖(monitor)可以認(rèn)為直接對應(yīng)底層操作系統(tǒng)中的互斥量(mutex)再愈。也就是說monitor依賴操作系統(tǒng)的MutexLock(互斥鎖)來實(shí)現(xiàn)榜苫,所以重量級鎖也稱為互斥鎖。這種同步方式的成本非常高践磅,包括系統(tǒng)調(diào)用引起的內(nèi)核態(tài)與用戶態(tài)切換单刁、線程阻塞造成的線程切換等。故這種監(jiān)視器鎖就被稱為重量級鎖府适。

為什么說重量級鎖的開銷大羔飞?
答:當(dāng)系統(tǒng)檢查到鎖是重量級鎖后,會把等待想要獲得鎖的線程進(jìn)行阻塞檐春,被阻塞的線程不消耗cpu逻淌,但是阻塞或者喚醒一個(gè)線程時(shí)都需要操作系統(tǒng)來幫忙,即從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)疟暖。這個(gè)轉(zhuǎn)換是要消耗很多時(shí)間的卡儒,有可能比用戶執(zhí)行代碼的時(shí)間還要長

小結(jié):synchronized并非一開始就給該對象加上重量級鎖俐巴,而是從偏向鎖到輕量級鎖再到重量級鎖的演變骨望。假如我們一開始就知道某個(gè)同步代碼塊競爭很激烈的話,那么我們一開始就要使用重量級鎖欣舵,從而減少鎖轉(zhuǎn)換的開銷擎鸠。如果我們只有一個(gè)線程在運(yùn)行,那偏向鎖則是一個(gè)很好的選擇缘圈。而當(dāng)某個(gè)同步代碼塊競爭不是那么很激烈的時(shí)候劣光,我們就可以考慮使用輕量級鎖袜蚕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市绢涡,隨后出現(xiàn)的幾起案子牲剃,更是在濱河造成了極大的恐慌,老刑警劉巖雄可,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凿傅,死亡現(xiàn)場離奇詭異,居然都是意外死亡数苫,警方通過查閱死者的電腦和手機(jī)狭归,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來文判,“玉大人过椎,你說我怎么就攤上這事∠凡郑” “怎么了疚宇?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赏殃。 經(jīng)常有香客問我敷待,道長,這世上最難降的妖魔是什么仁热? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任榜揖,我火速辦了婚禮,結(jié)果婚禮上抗蠢,老公的妹妹穿的比我還像新娘举哟。我一直安慰自己,他們只是感情好迅矛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布妨猩。 她就那樣靜靜地躺著,像睡著了一般秽褒。 火紅的嫁衣襯著肌膚如雪壶硅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天销斟,我揣著相機(jī)與錄音庐椒,去河邊找鬼。 笑死蚂踊,一個(gè)胖子當(dāng)著我的面吹牛约谈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼窗宇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了特纤?” 一聲冷哼從身側(cè)響起军俊,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捧存,沒想到半個(gè)月后粪躬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昔穴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年镰官,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吗货。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泳唠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宙搬,到底是詐尸還是另有隱情笨腥,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布勇垛,位于F島的核電站脖母,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闲孤。R本人自食惡果不足惜谆级,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望讼积。 院中可真熱鬧肥照,春花似錦、人聲如沸勤众。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽决摧。三九已至亿蒸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掌桩,已是汗流浹背边锁。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留波岛,地道東北人茅坛。 一個(gè)月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贡蓖。 傳聞我的和親對象是個(gè)殘疾皇子曹鸠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351