摘要:撤銷鎖偏向鎖使用了一種等到競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖。輕量級(jí)鎖線程在執(zhí)行同步代碼塊之前,會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的復(fù)制到鎖記錄中,官方稱為。
前言
作者前面也寫了幾篇關(guān)于Java并發(fā)編程,以及線程和volatil的基礎(chǔ)知識(shí),有興趣可以閱讀作者的原文博客,今天關(guān)于Java中的兩種鎖進(jìn)行詳解,希望對(duì)你有所幫助
本文受趙sir原創(chuàng)發(fā)布,轉(zhuǎn)載請(qǐng)聯(lián)系原創(chuàng)為什么使用synchronized
https://blog.csdn.net/qq_3609...
在上一章中說了volatile,在多線程下可以保證變量的可見性,但是不能保證原子性,下面一段代碼說明:
運(yùn)行上面代碼,會(huì)發(fā)現(xiàn)輸出flag的值不是理想中10000,雖然volatile寫入時(shí)候會(huì)通知其他線程的工作內(nèi)存值無效,從主內(nèi)存重寫讀取。i++是三步操作,讀取-賦值-寫入不能保證原子性。原子性:不能被中斷要么成功要么失敗。
比如此時(shí)主內(nèi)存的flag值10,線程1和線程2讀取到自己工作內(nèi)存都是10,然后線程1在進(jìn)行賦值的時(shí)候,線程2執(zhí)行了,這時(shí)線程2發(fā)現(xiàn)自己內(nèi)存的值和主內(nèi)存的值一樣,并沒有修改,然后賦值寫入11,此時(shí)線程1運(yùn)行,因?yàn)橹白x過了,會(huì)往下繼續(xù)運(yùn)行寫入也是11。那么兩個(gè)線程相當(dāng)于只增加了一次。要想達(dá)到理想值,只需要修改public synchronized void increase() { flag++; }就行了。
什么是synchronizedJava提供的一種原子性性內(nèi)置鎖,Java每個(gè)對(duì)象都可以把它當(dāng)做是監(jiān)視器鎖,線程代碼執(zhí)行在進(jìn)入synchronized代碼塊時(shí)候會(huì)自動(dòng)獲取內(nèi)部鎖,這個(gè)時(shí)候其他線程訪問時(shí)候會(huì)被阻塞到隊(duì)列,直到進(jìn)入synchronized中的代碼執(zhí)行完畢或者拋出異常或者調(diào)用了wait方法,都會(huì)釋放鎖資源。在進(jìn)入synchronized會(huì)從主內(nèi)存把變量讀取到自己工作內(nèi)存,在退出的時(shí)候會(huì)把工作內(nèi)存的值寫入到主內(nèi)存,保證了原子性。
synchronized機(jī)制編譯后執(zhí)行javap -v Test.class就會(huì)發(fā)現(xiàn)兩條指令。
synchronized是使用一種monitor機(jī)制,在進(jìn)入鎖時(shí)候先執(zhí)行monitorenter指令。退出的時(shí)候執(zhí)行monitorexit指令。synchronized是可重入鎖,每個(gè)對(duì)象中都含有一個(gè)計(jì)數(shù)器當(dāng)前線程再次獲取鎖,計(jì)數(shù)器+1,退出時(shí)候計(jì)算器-1,直到計(jì)數(shù)器為0才釋放鎖資源,喚醒其他線程來爭(zhēng)搶資源。任意一個(gè)對(duì)象都擁有自己的監(jiān)視器,只有在線程獲取到監(jiān)視器鎖時(shí)才會(huì)進(jìn)入代碼中,否則就進(jìn)入阻塞狀態(tài)。
synchronized使用場(chǎng)景對(duì)于普通方法,鎖是當(dāng)前類實(shí)例對(duì)象。
對(duì)于靜態(tài)方法,鎖是當(dāng)前類對(duì)象。
對(duì)于同步代碼塊,鎖是synchronized括號(hào)里的對(duì)象。
synchronized鎖升級(jí)synchronized在1.6以前是重量級(jí)鎖,當(dāng)前只有一個(gè)線程執(zhí)行,其他線程阻塞。為了減少獲得鎖和釋放鎖帶來的性能問題,而引入了偏向鎖、輕量級(jí)鎖以及鎖的存儲(chǔ)過程和升級(jí)過程。在1.6后鎖分為了無鎖、偏向鎖、輕量鎖、重量鎖,鎖的狀態(tài)在多線程競(jìng)爭(zhēng)的情況下會(huì)逐漸升級(jí),只能升級(jí)而不能降級(jí),這樣是為了提高鎖獲取和釋放的效率。
synchronized的鎖是存貯在Java對(duì)象頭里的,如果對(duì)象是數(shù)組類型,則虛擬機(jī)用3個(gè)字寬(Word)存儲(chǔ)對(duì)象頭,如果對(duì)象是非數(shù)組類型,則用2字寬存儲(chǔ)對(duì)象頭。1個(gè)字寬等于4個(gè)字節(jié)。
Java對(duì)象頭中的Mark Word里默認(rèn)存儲(chǔ)了對(duì)象是HashCode、分代年齡、和鎖標(biāo)記。
在運(yùn)行的時(shí)候,Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化,可能會(huì)變化為存儲(chǔ)以下四種形式。
偏向鎖的意思未來只有一個(gè)線程使用鎖,不會(huì)有其他線程來爭(zhēng)取。
獲取鎖:
首先檢查Mark word中鎖的標(biāo)志是否為01。
如果是01,判斷對(duì)象頭的Mark word記錄是否為當(dāng)前線程ID,如果是執(zhí)行5,否則執(zhí)行3.
線程ID并未只指向自己,發(fā)送CAS競(jìng)爭(zhēng),如果競(jìng)爭(zhēng)成功,則將Mark Word中線程ID設(shè)置為當(dāng)前線程ID,執(zhí)行5;如果未成功執(zhí)行4。
當(dāng)?shù)竭_(dá)全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒有正在執(zhí)行的字節(jié)碼)時(shí)獲得偏向鎖的線程被掛起,偏向鎖升級(jí)為輕量級(jí)鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼。
執(zhí)行同步代碼。
撤銷鎖:偏向鎖使用了一種等到競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖。需要等待全局安全點(diǎn),它首先暫停原持有偏向鎖的線程,然后檢查線程是否還在活著,如果線程處于未活動(dòng)狀態(tài),則釋放鎖標(biāo)記,如果處于活動(dòng)狀態(tài)則升級(jí)為輕量級(jí)鎖。
CASCAS全稱是Compare And Swap 即比較并交換,使用樂觀鎖機(jī)制,包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。 如果內(nèi)存位置的值與預(yù)期原值相匹配,那么才會(huì)將該位置值更新為新值 。否則,處理器不做任何操作。
輕量級(jí)鎖線程在執(zhí)行同步代碼塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。
加鎖:
CAS修改Mark Word,如果成功指向棧中鎖記錄的指針執(zhí)行3,如果失敗執(zhí)行2.
發(fā)生自旋,自旋到一定次數(shù),如果修改成功執(zhí)行3,否則鎖膨脹為重量級(jí)鎖。
執(zhí)行同步代碼塊。
解鎖:
輕量級(jí)解鎖時(shí),會(huì)使用原子的CAS操作將Displaced Mark Word替換回到對(duì)象頭,如果成功,則表示沒有競(jìng)爭(zhēng)發(fā)生。如果失敗,表示當(dāng)前鎖存在競(jìng)爭(zhēng),鎖就會(huì)膨脹成重量級(jí)鎖。
它是在1.5之后提供的一個(gè)獨(dú)占鎖接口,它的實(shí)現(xiàn)類是ReentrantLock,相比較synchronized這種隱式鎖(不用手動(dòng)加鎖和釋放鎖)的便捷性,但是提供了更加鎖的可操作性、可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized不具備的特性。
使用方法在finally中釋放鎖,目的保證獲取鎖最終被釋放。不要在獲取鎖寫在try里,因?yàn)槿绻讷@取鎖時(shí)發(fā)生了異常,異常拋出的同時(shí),也會(huì)導(dǎo)致鎖無故釋放。
AQSAQS是隊(duì)列同步器(AbstractQueuedSynchronizer),是用來構(gòu)建鎖或者其他同步器的基礎(chǔ)框架,它使用了一個(gè)int成員變量表示同步狀態(tài),通過內(nèi)置的FIFO隊(duì)列來完成資源獲取的線程排隊(duì)工作問題。AQS在內(nèi)部維護(hù)了一個(gè)單一的狀態(tài)信息state,可以通過getState、setState、compareAndSetState(CAS操作)修改此值,對(duì)于ReentrantLock來說,state可以用來表示當(dāng)前線程獲取鎖的可重入次數(shù)。ReentrantLock中當(dāng)一個(gè)線程獲取了鎖,在AQS的內(nèi)部會(huì)進(jìn)行compareAndSetState將state變?yōu)?,如果再次獲取就設(shè)置為2,釋放鎖也會(huì)去修改state值,只有當(dāng)值變?yōu)?時(shí),其他線程才能獲得鎖。
鎖的介紹AQS底層維護(hù)state和隊(duì)列來實(shí)現(xiàn)獨(dú)占和共享兩種鎖。
獨(dú)占鎖:每次只能有一個(gè)線程能持有鎖,如lock、synchronized。
共享鎖:允許多個(gè)線程同時(shí)獲取鎖,并發(fā)訪問共享資源,如ReadWriteLock。
lock分為公平鎖和非公平鎖,實(shí)現(xiàn)了AQS接口,通過FIFO設(shè)置鎖的優(yōu)先級(jí)。
公平鎖:根據(jù)線程獲取鎖的時(shí)間來判斷,等待時(shí)間越久的線程優(yōu)先被執(zhí)行。Lock中初始化的時(shí)候ReentrantLock(true),默認(rèn)為false,效率較低因?yàn)樾枰袛嗑€程的等待時(shí)間。
非公平鎖:搶占鎖資源,不能保證獲取鎖的線程優(yōu)先級(jí),效率較高,因?yàn)楂@取鎖是競(jìng)爭(zhēng)的。
兩者不同synchronized是Java的關(guān)鍵字,lock是提供的類。
synchronized提供不需要手動(dòng)加鎖和釋放的隱式鎖,釋放鎖的條件是代碼執(zhí)行完或者拋出異常自動(dòng)釋放。lock必須手動(dòng)加鎖和釋放鎖,另外還提供了可中斷鎖、超時(shí)獲取鎖、判斷鎖狀態(tài)。
synchronized是可重入、不可中斷、非公平,lock是可重入、可中斷、公平(兩者皆可)
synchronized適合代碼量少的同步,lock適合代碼量同步多的。**
Condition接口還記得在Java并發(fā)二中有一道生產(chǎn)者消費(fèi)者,使用的是synchronized+wait(notify),lock中也提供了這種等待通知類型的方法await和signal,當(dāng)前線程調(diào)用這些方法時(shí),需要提前獲取到Condition對(duì)象關(guān)聯(lián)的鎖,Condition是依賴于Lock對(duì)象,調(diào)用lock對(duì)象中的newCondition。
老樣子還是先定義一個(gè)容器:
生產(chǎn)者:?jiǎn)?個(gè)線程往容器里添加數(shù)據(jù)。
消費(fèi)者:?jiǎn)?0線程消費(fèi)數(shù)據(jù)
最后注釋基本明確,就不多說了。wait和notify是配合synchronized使用,await和signal是配合lock使用,區(qū)別在于喚醒時(shí)notify不能指定線程喚醒,signal可以喚醒具體的線程,更小的粒度控制鎖。
閱讀更多金三銀四,2019最新面試實(shí)戰(zhàn)總結(jié)
如何通過抓包實(shí)戰(zhàn)來學(xué)習(xí)Web協(xié)議?
動(dòng)畫:一招學(xué)會(huì)TCP的三次握手和四次揮手
上兩個(gè)月,15家面試,幾個(gè)offer , 我的面試歷程!
相信自己,沒有做不到的,只有想不到的在這里獲得的不僅僅是技術(shù)!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/77721.html
摘要:為什么叫重入鎖呢,我們把它拆開來看就明了了。釋放鎖,每次鎖持有者數(shù)量遞減,直到為止。 相信大家在工作或者面試過程中經(jīng)常聽到重入鎖這個(gè)概念,或者與關(guān)鍵字 synchrozied 的對(duì)比,棧長(zhǎng)面試了這么多人,80%的面試者都沒有答對(duì)或沒有答到點(diǎn)上,或者把雙重效驗(yàn)鎖搞混了,哭笑不得。。 那么你對(duì)重入鎖了解有多少呢?今天,棧長(zhǎng)幫大家撕開重入鎖的面紗,來見識(shí)下重入鎖的真實(shí)容顏。。 什么是重入鎖 ...
摘要:記得幾年前有一次棧長(zhǎng)去面試,問到了這么一個(gè)問題中的對(duì)象都是在堆中分配嗎說明為什么當(dāng)時(shí)我被問得一臉蒙逼,瞬間被秒殺得體無完膚,當(dāng)時(shí)我壓根就不知道他在考什么知識(shí)點(diǎn),難道對(duì)象不是在堆中分配嗎最后就沒然后了,回去等通知了。。 記得幾年前有一次棧長(zhǎng)去面試,問到了這么一個(gè)問題: Java中的對(duì)象都是在堆中分配嗎?說明為什么! 當(dāng)時(shí)我被問得一臉蒙逼,瞬間被秒殺得體無完膚,當(dāng)時(shí)我壓根就不知道他在考什么...
摘要:算法名稱描述優(yōu)點(diǎn)缺點(diǎn)標(biāo)記清除算法暫停除了線程以外的所有線程算法分為標(biāo)記和清除兩個(gè)階段首1 回顧我的時(shí)間線 在本文的開頭,先分享一下自己的春招經(jīng)歷吧: 各位掘友大家好,我是練習(xí)時(shí)長(zhǎng)快一年的Android小蔡雞,喜歡看源碼,逛掘金,寫技術(shù)文章...... 好了好,不開玩笑了OWO,今年春招投了許多簡(jiǎn)歷的,但是被撈的只有阿里,頭條和美團(tuán),一路下來挺不容易的. 個(gè)人認(rèn)為在春招中運(yùn)氣>性格>三觀>技術(shù)...
摘要:所以,剛開始我并沒有直接就投遞阿里,畢竟心里還是有一點(diǎn)點(diǎn)小害怕的。操作系統(tǒng)的內(nèi)存管理機(jī)制進(jìn)程和線程的區(qū)別說下你對(duì)線程安全的理解有什么作用,和有什么區(qū)別實(shí)現(xiàn)原理用過么什么場(chǎng)景下用的底層原理。 作者:ppxyn。本文來自讀者投稿。該文已加入筆主的開源項(xiàng)目——JavaGuide(一份涵蓋大部分Java程序員所需要掌握的核心知識(shí)的文檔類項(xiàng)目),地址:https://github.com/Sna...
閱讀 3052·2021-09-23 11:32
閱讀 3006·2021-09-22 15:12
閱讀 1771·2019-08-30 14:07
閱讀 3527·2019-08-29 16:59
閱讀 1718·2019-08-29 11:11
閱讀 2375·2019-08-26 13:50
閱讀 2480·2019-08-26 13:49
閱讀 2671·2019-08-26 11:49