深入JVM-鎖與并發(fā)

一孽水、鎖在Java虛擬機中的實現(xiàn)與優(yōu)化

1.1 偏向鎖

偏向鎖是JDK 1.6 提出的一種鎖優(yōu)化方式躏救。其核心思想是,如果程序沒有競爭姜凄,則取消之前已經(jīng)取得鎖的線程同步操作。也就說趾访,若某一鎖被線程獲取后态秧,便進入偏向模式,當線程再次請求這個鎖時扼鞋,無需進行相關的同步操作申鱼,從而節(jié)省了操作時間。如果在此之前有其他線程進行了鎖請求藏鹊,則鎖退出偏向模式润讥。在JVM中使用-XX:+UseBiasedLocking可以設置啟用偏向鎖。

當鎖對象處于偏向模式時盘寡,對象頭會記錄獲取鎖的線程

[JavaThread* | epoch | age | 1 | 01]

這樣楚殿,當該線程再次嘗試獲得鎖時,通過Mark Word的線程信息就可以判斷當前線程是否持有偏向鎖竿痰。

偏向鎖在鎖競爭激烈的場合沒有太強的優(yōu)化效果脆粥,因為大量的競爭會導致持有鎖的線程不停地切換,鎖也很難一直保持在偏向模式影涉,此時变隔,使用鎖偏向不僅得不到性能的優(yōu)化,反而有可能降低系統(tǒng)性能蟹倾。

1.2 輕量級鎖

如果偏向鎖失敗匣缘,Java虛擬機會讓線程申請輕量級鎖。輕量級鎖在虛擬機內部鲜棠,使用一個稱謂BasicObjectLock的對象實現(xiàn)肌厨,這個對象內部由一個BasicLock對象和一個持有該鎖的Java對象指針組成。BasicObjectLock對象放置在Java棧的棧幀中豁陆。在BasicLock對象內部還維護者displaced_header字段柑爸,他用于備份對象頭部的Mark Word。

當一個線程持有一個對象的鎖時盒音,對象頭部Mark Word如下:

[ptr       | 00]  locked

末尾兩位比特為00表鳍,整個Mark Word為指向BasicLock對象的指針。由于BasicObjectLock對象在線程棧中祥诽,因此該指針必然指向持有該鎖的線程椘┦ィ空間。當需要判斷某一線程是否持有該對象鎖時原押,也只需簡單的判斷對象頭的指針是否在當前線程的棧地址范圍即可胁镐。同時,BasicLock對象的displaced_header字段,備份了元對象的Mark Word內存盯漂。BasicObjectLock對象的obj字段則指向該對象颇玷。

1.3 鎖膨脹

當輕量級鎖失敗,虛擬機就會使用重量級鎖就缆。在使用重量級鎖時帖渠,對象的Mark Word如下:

[ptr        | 10] monitor

末尾的2比特標記位被置為10。整個Mark Word表示指向monitor對象的指針竭宰。

1.4 自旋鎖

鎖膨脹后空郊,進入ObjectMonitor的enter(),線程很可能會在操作系統(tǒng)層面被掛起切揭,這樣線程上下文切換的性能損失就比較大狞甚。因此,在鎖膨脹后廓旬,虛擬機會做最后的爭取哼审,希望線程可以盡快進入臨界區(qū)而避免被操作系統(tǒng)掛起。一種較為有效的手段就是使用自旋鎖孕豹。

自旋鎖可以使線程在沒有取得鎖時涩盾,不被掛起,而轉而去執(zhí)行一個空循環(huán)(即所謂的自旋)励背,在若干個空循環(huán)后春霍,線程如果可以獲得鎖,則繼續(xù)執(zhí)行叶眉。若線程依然不能獲得鎖址儒,才會被掛起。

使用自旋鎖后衅疙,線程被掛起的幾率相對減少离福,線程執(zhí)行的連貫性相對加強。因此炼蛤,對于那些鎖競爭不是很激烈,鎖占用時間很短的并發(fā)線程蝶涩,具有一定的積極意義理朋,但對于鎖競爭激烈,單線程鎖占用時間長的并發(fā)程序绿聘,自旋鎖在自旋等待后嗽上,往往依然無法獲得對應的鎖,不僅僅白白浪費了CPU時間熄攘,最終還是免不了執(zhí)行被掛起的操作兽愤,反而浪費了系統(tǒng)資源。

在JDK 1.6 中,Java虛擬機提供-XX:+UseSpinning參數(shù)來開啟自旋鎖浅萧,使用-XX:PreBlockSpin參數(shù)來設置自旋鎖的等待次數(shù)逐沙。

在JDK 1.7中,自旋鎖的參數(shù)被取消洼畅,虛擬機不再支持由用戶配置自旋鎖吩案。自旋鎖總是會執(zhí)行,自旋次數(shù)也由虛擬機自行調整帝簇。

1.5 鎖消除

鎖消除是Java虛擬機在JIT編譯時徘郭,通過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖丧肴。通過鎖消除残揉,可以節(jié)省毫無意義的請求鎖時間。

二芋浮、鎖在應用層的優(yōu)化思路

2.1 減少鎖持有時間

public Matcher matcher(CharSequence input) {
    if (!compiled) {
        synchronized(this) {
            if (!compiled)
                compile();
        }
    }
    Matcher m = new Matcher(this, input);
    return m;
}

2.2 減小鎖粒度

典型的場景就是ConcurrentHashMap類的實現(xiàn)抱环。ConcurrentHashMap將整個HshMap分成若干段(Segment),每個段都是一個子HashMap途样。

如果需要在ConcurrentHashMap中增加一個新的表項江醇,并不是將整個HashMap加鎖,而是首先根據(jù)hashcode得到該表項應該被存放到哪個段中何暇,然后對該段加鎖陶夜,并完成put()操作。在多線程環(huán)境中裆站,如果多個線程同時進行put()操作条辟,只要被加入的表項不存放在同一個段中,則線程間可以做到真正的并行宏胯。

2.3 鎖分離

鎖分離是減小鎖粒度的一個特例羽嫡。他依據(jù)應用程序的功能特點,將一個獨占鎖分成多個鎖肩袍。一個典型的案例就是java.util.concurrent.LinkedBlockingQueue的實現(xiàn)杭棵。

在LinkedBlockingQueue的實現(xiàn)中,take()函數(shù)和put()函數(shù)分別實現(xiàn)了從隊列中取得數(shù)據(jù)和往隊列中zeng增加數(shù)據(jù)的功能氛赐。雖然兩個函數(shù)都對當前隊列進行了修改操作魂爪,但由于LinkedBlockingQueue是基于鏈表的,因此艰管,兩個操作分別作用于隊列的前端和尾端滓侍,從理論上來說,兩者并不沖突牲芋。

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }

2.4 鎖粗化

通常情況下撩笆,為了保證多線程間的有效并發(fā)捺球,會要求每個線程持有鎖的時間盡量短,即在使用完公共資源后夕冲,應該立即釋放鎖氮兵。只有這樣,等待在這個鎖上的其他線程才能盡早的獲得資源執(zhí)行任務耘擂。但是胆剧,凡事都有一個度,如果對同一個鎖不停地進行請求醉冤、同步和釋放秩霍,其本身也會消耗系統(tǒng)寶貴的資源,反而不利于性能的優(yōu)化蚁阳。

為此铃绒,虛擬機在遇到一連串連續(xù)的對同一鎖不斷進行請求和釋放的操作時,便會把所有的鎖操作整合成對鎖的一次請求螺捐,從而減少對鎖的請求同步次數(shù)颠悬。這個操作叫做鎖的粗化。

三定血、無鎖

可以使用yi'z一種稱為非阻塞同步的方法赔癌,這種方法不需要使用“鎖”(因此稱之為無鎖),但是依然能確保數(shù)據(jù)和程序在高并發(fā)環(huán)境下澜沟,保持多線程間的一致性灾票。

3.1 理解CAS

CAS算法的過程是這樣:它包含3個參數(shù)CAS(V,E,N)。V表示要更新的變量茫虽,E表示預期值刊苍,N表示新值。僅當V值=E值時濒析,才會將V的值設為N正什,如果V值和E值不同,則說明已經(jīng)有其他線程做了更新号杏,則當前線程什么都不做婴氮。最后,CAS返回當前V的真實值盾致。

3.2 原子操作

為了能讓CAS操作被Java應用程序充分使用莹妒,在JDK的java.util.concurrent.atomic包下,有一組使用無鎖算法實現(xiàn)的原子操作類绰上,主要有AtomicInteger、AtomicIntegerArray渠驼、AtomicLong蜈块、AtomicLongArray和AtomicReference等。他們分別封裝了對整數(shù)、整數(shù)數(shù)組百揭、長整型爽哎、長整型數(shù)組和普通對象的多線程安全操作。

3.3 LongAdder

在JDK 1.8中引入了LongAdder器一。結合減小鎖粒度與ConcurrentHashMap的實現(xiàn)课锌,我們可以想到一種對傳統(tǒng)AtomicInteger等原子類的改進思路。雖然在CAS操作中沒有鎖祈秕,但是像減小鎖粒度這種分離熱點的思路依然可以使用渺贤。一種可行的方案就是仿造ConcurrentHashMap,將熱點數(shù)據(jù)分離请毛。比如志鞍,可以將AtomicInteger的內部核心數(shù)據(jù)value分離成一個數(shù)組,每個線程訪問時方仿,通過哈希等算法映射到其中一個數(shù)字進行計數(shù)固棚,而最終的技術結果,則為這個數(shù)組的求和累加仙蚜。其中此洲,熱點數(shù)據(jù)value被分離成多個單元cell,每個cell獨自維護內部的值委粉,當前對象的實際值由所有的cell累計合成呜师,這樣,熱點就進行了有效的分離艳丛,提高了并行度匣掸。LongAdder正是使用了這種思想。

四氮双、理解Java內存模型

  • 原子性
  • 有序性
  • 可見性
  • Happens-Before原則
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末碰酝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子戴差,更是在濱河造成了極大的恐慌送爸,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暖释,死亡現(xiàn)場離奇詭異袭厂,居然都是意外死亡,警方通過查閱死者的電腦和手機球匕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門纹磺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亮曹,你說我怎么就攤上這事橄杨∶刂ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵式矫,是天一觀的道長乡摹。 經(jīng)常有香客問我,道長采转,這世上最難降的妖魔是什么聪廉? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮故慈,結果婚禮上板熊,老公的妹妹穿的比我還像新娘。我一直安慰自己惯悠,他們只是感情好邻邮,可當我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著克婶,像睡著了一般筒严。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上情萤,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天鸭蛙,我揣著相機與錄音,去河邊找鬼筋岛。 笑死娶视,一個胖子當著我的面吹牛,可吹牛的內容都是我干的睁宰。 我是一名探鬼主播肪获,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柒傻!你這毒婦竟也來了孝赫?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤红符,失蹤者是張志新(化名)和其女友劉穎青柄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體预侯,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡致开,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了萎馅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片双戳。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖糜芳,靈堂內的尸體忽然破棺而出飒货,到底是詐尸還是另有隱情千诬,我是刑警寧澤,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布膏斤,位于F島的核電站,受9級特大地震影響邪驮,放射性物質發(fā)生泄漏莫辨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一毅访、第九天 我趴在偏房一處隱蔽的房頂上張望沮榜。 院中可真熱鬧,春花似錦喻粹、人聲如沸蟆融。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽型酥。三九已至,卻和暖如春查乒,著一層夾襖步出監(jiān)牢的瞬間弥喉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工玛迄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留由境,地道東北人。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓蓖议,卻偏偏與公主長得像虏杰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子勒虾,可洞房花燭夜當晚...
    茶點故事閱讀 45,982評論 2 361

推薦閱讀更多精彩內容