java多線程-鎖

我們都知道以舒,多個(gè)線程并發(fā)訪問共享變量或者共享資源就回來帶來線程安全問題毒涧。
于是就可以想到一種保障線程安全的方法--將多個(gè)線程的并發(fā)訪問轉(zhuǎn)換位串行訪問,即一個(gè)共享數(shù)據(jù)一次只能被一個(gè)線程訪問奠旺。這個(gè)就是鎖了舌缤。

java平臺(tái)的鎖包括內(nèi)部鎖和顯式鎖。
內(nèi)部鎖是通過synchronized關(guān)鍵字實(shí)現(xiàn)的黔帕,顯式鎖是通過Lock接口的實(shí)現(xiàn)類實(shí)現(xiàn)的。

鎖的作用:保障原子性蹈丸,可見性成黄,和有序性。

  • 鎖是通過互斥保障原子性的逻杖,因?yàn)殒i一次只能被一個(gè)線程持有奋岁,就保證了臨界區(qū)代碼一次只能被一個(gè)線程執(zhí)行,這使得臨界區(qū)代碼所執(zhí)行的操作具有不可分割的特性荸百,即具備原子性闻伶。
  • 鎖的獲得隱含著刷新處理器緩存這個(gè)動(dòng)作,即執(zhí)行臨界區(qū)代碼前够话,可以將寫線程對共享變量所做的更新同步到該線程執(zhí)行處理器的高速緩存中蓝翰。而鎖的釋放隱含著沖刷處理器緩存這個(gè)動(dòng)作。使得寫線程對共享變量所作的更新能夠被推送到該線程執(zhí)行處理器的高速緩存中女嘲,從而對讀線程可同步畜份。因此,鎖能夠保證可見性欣尼。
  • 由于鎖對可見性的保證爆雹,寫線程在臨界區(qū)中對任何一個(gè)共享變量所做的更新都對線程可見。由于臨界區(qū)內(nèi)的操作具有原子性,因此寫線程對共享變量的更新同時(shí)對讀線程可見

synchronized關(guān)鍵字

  • synchronized可以用來修飾方法或者代碼塊
  • 作為鎖句柄的變量通常用final修飾钙态,這是因?yàn)殒i句柄變量的值一旦改變慧起,會(huì)導(dǎo)致執(zhí)行同一個(gè)同步快的多個(gè)線程實(shí)際上使用不同的鎖,從而導(dǎo)致競態(tài)册倒。
  • 線程對內(nèi)部鎖的申請與釋放的動(dòng)作由java虛擬機(jī)負(fù)責(zé)代為實(shí)施蚓挤。

內(nèi)部鎖的調(diào)度:java虛擬機(jī)為每個(gè)內(nèi)部鎖分配一個(gè)入口集,用于記錄等待獲得相應(yīng)內(nèi)部鎖的線程剩失。多個(gè)線程申請同一個(gè)鎖的時(shí)候屈尼,只有一個(gè)申請者能申請成為該鎖的持有線程,而其他申請者的申請操作會(huì)失敗拴孤。申請失敗的線程會(huì)被暫停并被存入相應(yīng)鎖的入口集中等待再次申請鎖的機(jī)會(huì)脾歧。當(dāng)線程申請的鎖被釋放時(shí),該鎖的入口集中的一個(gè)任意線程會(huì)被java虛擬機(jī)喚醒演熟,從而得到再次申請鎖的機(jī)會(huì)鞭执。

  • 原理
    1.理解Java對象頭與Monitor
    在JVM中,對象在內(nèi)存中的布局分為三塊區(qū)域:對象頭芒粹、實(shí)例數(shù)據(jù)和對齊填充兄纺,如圖:
    image.png

    實(shí)例變量:存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息化漆,如果是數(shù)組的實(shí)例部分還包括數(shù)組的長度估脆,這部分內(nèi)存按4字節(jié)對齊。
    對齊填充:由于虛擬機(jī)要求對象得起始地址必須是8字節(jié)的整數(shù)倍座云。填充數(shù)據(jù)不是必須存在的疙赠,僅僅是為了字節(jié)對齊。
    對象頭:包含Mark word和class metadata address兩部分朦拖。
    mark work的存儲(chǔ)內(nèi)容如下
    image.png

    metadata address:即元數(shù)據(jù)的指針圃阳,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對象是哪個(gè)類的實(shí)例。

如果對象是數(shù)組璧帝,那對象頭中還必須有一塊用于記錄數(shù)組長度的數(shù)據(jù)捍岳,因?yàn)樘摂M機(jī)可以通過普通java對象的元數(shù)據(jù)信息確定java對象的大小,但是從數(shù)組的元數(shù)據(jù)中無法確定數(shù)組的大小睬隶。

mark work里面的重量級鎖锣夹,也就是我們通常說的synchronized的對象鎖,鎖標(biāo)識(shí)位為10理疙,其中指針指向的是monitor對象(也稱為管程或者監(jiān)視器鎖)的起始地址晕城。每個(gè)對象都存在一個(gè)monitor與之關(guān)聯(lián),monitor可以與對象一起創(chuàng)建銷毀窖贤,也可以當(dāng)線程試圖獲取對象鎖時(shí)自動(dòng)生成砖顷,當(dāng)一個(gè)monitor被某個(gè)線程持有后贰锁,它便處于鎖定狀態(tài)。
monitor的實(shí)現(xiàn)
monitor其實(shí)是一種同步工具滤蝠,被描述為一個(gè)對象豌熄,他的義務(wù)是保證只有一個(gè)線程可以訪問被保護(hù)的數(shù)據(jù)和代碼。
在HotSpot中物咳,monitor是基于c++實(shí)現(xiàn)的锣险,由ObjectMonitor實(shí)現(xiàn),結(jié)構(gòu)如下

ObjectMonitor() {
    _header       = NULL;
    _count        = 0;//用來記錄該線程獲取鎖的次數(shù)
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;//指向持有ObjectMonitor對象的線程
    _WaitSet      = NULL;//存放處于wait狀態(tài)的線程隊(duì)列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;//鎖的重入次數(shù)
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;//存放處于等待鎖block狀態(tài)的線程隊(duì)列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

當(dāng)多個(gè)線程同時(shí)訪問一段同步代碼的時(shí)候览闰,首先會(huì)進(jìn)入_EntryList隊(duì)列中芯肤,當(dāng)某個(gè)線程獲取到對象的monitor后進(jìn)入_Owner區(qū)域,并把monitor中的_owner變量設(shè)置為當(dāng)前線程压鉴,同時(shí)monitor中的計(jì)數(shù)器_count加1崖咨,即獲得對象鎖

若持有monitor的線程調(diào)用wait()方法,將釋放當(dāng)前持有的monitor,_owner變量恢復(fù)為null,_count自減1油吭,同時(shí)該線程進(jìn)入_WaitSet集合中等待被喚醒击蹲。若當(dāng)前線程執(zhí)行完畢也將釋放monitor并復(fù)位變量的值,以便其他線程進(jìn)入獲取monitor.
以下是獲取鎖的過程:


image.png

monitor對象存在于每個(gè)java對象的對象頭中婉宰,synchronized鎖便是通過這種方式獲取鎖的歌豺,這也是為什么java中任意對象可以作為鎖的原因。
同時(shí)也是notify/notifyAll/wait等方法存在于頂級對象object中的原因心包,在使用這幾個(gè)方法時(shí)类咧,必須處于synchronized代碼塊或者synchronized方法中,否則就會(huì)拋出IllegalMonitorStateException異常蟹腾,因?yàn)檎{(diào)用這幾個(gè)方法前必須拿到當(dāng)前對象的監(jiān)視器monitor對象轮听,也就是說notify/notifyAll/wait這幾個(gè)方法依賴于monitor對象,而要獲取monitor,就必須通過synchronized關(guān)鍵字岭佳,這也就是notify/notifyAll/wait方法必須在synchronized代碼塊或者synchronized方法調(diào)用的原因了。

synchronized的實(shí)現(xiàn)原理

public class SynchronizedDemo {
//同步方法
    public synchronized void doSth(){
        System.out.println("Hello World");
    }

//同步代碼塊
    public void doSth1(){
        synchronized (SynchronizedDemo.class){
            System.out.println("Hello World");
        }
    }
}

看一下上面代碼的字節(jié)碼萧锉,如下

public synchronized void doSth();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
  public void doSth1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #5                  // class com/hollis/SynchronizedTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #3                  // String Hello World
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: return

通過反編譯后代碼可以看出珊随,對于同步方法罗捎,JVM采用ACC_SYNCHRONIZED標(biāo)記符來實(shí)現(xiàn)同步达皿。對于同步代碼塊,JVM采用monitorenter,monitorexit兩個(gè)指令來實(shí)現(xiàn)同步寸爆。

當(dāng)某個(gè)線程要訪問某個(gè)方法的時(shí)候禀崖,會(huì)檢查是否有ACC_SYNCHRONIZED衩辟,如果有設(shè)置,則需要先獲得監(jiān)視器波附,然后再執(zhí)行方法艺晴,方法執(zhí)行后釋放監(jiān)視器鎖昼钻。如果在方法執(zhí)行過程中,發(fā)生異常封寞,并且方法內(nèi)部沒有處理該異常然评,那么異常被拋到方法外面之前監(jiān)視器鎖會(huì)自動(dòng)釋放。

同步代碼塊使用monitorenter和monitorexit兩個(gè)指令實(shí)現(xiàn)狈究⊥胩剩可以把執(zhí)行monitorenter指令理解為加鎖,執(zhí)行monitorexit理解為釋放鎖抖锥。每個(gè)對象維護(hù)著一個(gè)記錄著被鎖次數(shù)得計(jì)數(shù)器亿眠。未被鎖定得對象得該計(jì)數(shù)器為0,當(dāng)一個(gè)線程獲得鎖后磅废,該計(jì)數(shù)器自增變?yōu)?纳像,當(dāng)一個(gè)線程再次獲得該對象得鎖時(shí),計(jì)數(shù)器再次自增还蹲。當(dāng)線程釋放鎖時(shí)爹耗,計(jì)數(shù)器自減。當(dāng)計(jì)數(shù)器為0的時(shí)候谜喊,鎖被釋放潭兽。

synchronized為什么被叫重量級鎖?
java的線程是映射到操作系統(tǒng)原生線程之上的斗遏,如果要阻塞或喚醒一個(gè)線程就需要操作系統(tǒng)的幫忙山卦,這就要從用戶態(tài)轉(zhuǎn)換到核心態(tài),因此狀態(tài)轉(zhuǎn)換需要花費(fèi)很多的處理器時(shí)間诵次,對于代碼簡單的同步快狀態(tài)轉(zhuǎn)換消耗的時(shí)間有可能比用戶代碼執(zhí)行的時(shí)間還要長账蓉,所以說是一個(gè)重量級的操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逾一,一起剝皮案震驚了整個(gè)濱河市铸本,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌遵堵,老刑警劉巖箱玷,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異陌宿,居然都是意外死亡锡足,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門壳坪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舶得,“玉大人,你說我怎么就攤上這事爽蝴°迮” “怎么了纫骑?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長珠插。 經(jīng)常有香客問我惧磺,道長,這世上最難降的妖魔是什么捻撑? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任磨隘,我火速辦了婚禮,結(jié)果婚禮上顾患,老公的妹妹穿的比我還像新娘番捂。我一直安慰自己,他們只是感情好江解,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布设预。 她就那樣靜靜地躺著,像睡著了一般犁河。 火紅的嫁衣襯著肌膚如雪鳖枕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天桨螺,我揣著相機(jī)與錄音宾符,去河邊找鬼。 笑死灭翔,一個(gè)胖子當(dāng)著我的面吹牛魏烫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肝箱,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼哄褒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了煌张?” 一聲冷哼從身側(cè)響起呐赡,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骏融,沒想到半個(gè)月后罚舱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绎谦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粥脚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窃肠。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖刷允,靈堂內(nèi)的尸體忽然破棺而出冤留,到底是詐尸還是另有隱情碧囊,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布纤怒,位于F島的核電站糯而,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泊窘。R本人自食惡果不足惜熄驼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烘豹。 院中可真熱鬧瓜贾,春花似錦、人聲如沸携悯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽憔鬼。三九已至龟劲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轴或,已是汗流浹背昌跌。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侮叮,地道東北人避矢。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像囊榜,于是被迫代替她去往敵國和親审胸。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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