java基礎(chǔ):java.util.concurrent.locks.ReentrantLock

前言

最近在使用BlockQueue和ConcurrentHashMap中都有個(gè)核心的東西ReentrantLock,網(wǎng)上有很多關(guān)于重入鎖的介紹。下面自己的幾個(gè)問(wèn)題陋气,然后再通過(guò)自己的實(shí)現(xiàn)來(lái)把問(wèn)題講清楚。

API官方解釋

一個(gè)可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語(yǔ)句所訪問(wèn)的隱式監(jiān)視器鎖相同的一些基本行為和語(yǔ)義匠童,但功能更強(qiáng)大。 ReentrantLock 將由最近成功獲得鎖塑顺,并且還沒有釋放該鎖的線程所"擁有"汤求。當(dāng)鎖沒有被另一個(gè)線程所擁有時(shí),調(diào)用 lock 的線程將成功獲取該鎖并返回严拒。如果當(dāng)前線程已經(jīng)擁有該鎖扬绪,此方法將立即返回】氵耄可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法來(lái)檢查此情況是否發(fā)生挤牛。
此類的構(gòu)造方法接受一個(gè)可選的公平 參數(shù)。當(dāng)設(shè)置為 true 時(shí)种蘸,在多個(gè)線程的爭(zhēng)用下墓赴,這些鎖傾向于將訪問(wèn)權(quán)授予等待時(shí)間最長(zhǎng)的線程竞膳。否則此鎖將無(wú)法保證任何特定訪問(wèn)順序。與采用默認(rèn)設(shè)置(使用不公平鎖)相比竣蹦,使用公平鎖的程序在許多線程訪問(wèn)時(shí)表現(xiàn)為很低的總體吞吐量(即速度很慢顶猜,常常極其慢),但是在獲得鎖和保證鎖分配的均衡性時(shí)差異較小痘括。不過(guò)要注意的是长窄,公平鎖不能保證線程調(diào)度的公平性。因此纲菌,使用公平鎖的眾多線程中的一員可能獲得多倍的成功機(jī)會(huì)挠日,這種情況發(fā)生在其他活動(dòng)線程沒有被處理并且目前并未持有鎖時(shí)。還要注意的是翰舌,未定時(shí)的 tryLock 方法并沒有使用公平設(shè)置嚣潜。因?yàn)榧词蛊渌€程正在等待,只要該鎖是可用的椅贱,此方法就可以獲得成功懂算。

典型用法

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

為什么理解ReentrantLock

  1. JDK里面的ReentrantLock實(shí)現(xiàn)非常精巧,ConcurrentHashMap庇麦,各種Queue都是基于ReentrantLock實(shí)現(xiàn)的计技,所以,了解ReentrantLock是十分必要的山橄。
  2. ReentrantLock使用非常靈活垮媒,synchronized是JVM實(shí)現(xiàn)的,用起來(lái)不那么靈活航棱。
  3. 了解內(nèi)部實(shí)現(xiàn)原理睡雇,可以更好的幫助我們?cè)陂_發(fā)中選型,也能更加容易理解并發(fā)包下其他實(shí)現(xiàn)類的原理饮醇。

ReentrantLock和synchronized

java中存在兩種鎖它抱,一種是JDK實(shí)現(xiàn)的synchronized,還有一種是Lock朴艰,兩者用法相似观蓄,又各有特點(diǎn),了解其中的區(qū)別呵晚,才能更好的取舍。

實(shí)現(xiàn)
  1. synchronized是基于JVM實(shí)現(xiàn)的沫屡,Lock是基于java API實(shí)現(xiàn)的饵隙,我們可以通過(guò)源碼就能知道Lock的實(shí)現(xiàn)。
  2. 兩者的實(shí)現(xiàn)思路一致沮脖,都是抽象出來(lái)一個(gè)同步隊(duì)列和一個(gè)等待隊(duì)列金矛,爭(zhēng)搶鎖的線程不斷在同步隊(duì)列和等待隊(duì)列中不斷轉(zhuǎn)換芯急。
  3. synchronized基于并發(fā)程度抽象出偏向鎖、輕量級(jí)鎖和重量級(jí)鎖驶俊,而Lock沒有這樣的概念娶耍。
使用
  1. 對(duì)于使用者的直觀體驗(yàn)上Lock是比較復(fù)雜的,需要lock和unlock饼酿,如果忘記釋放鎖就會(huì)產(chǎn)生死鎖的問(wèn)題榕酒,通常需要在finally中進(jìn)行鎖的釋放。但是synchronized的使用十分簡(jiǎn)單故俐,只需要對(duì)自己的方法或者關(guān)注的同步對(duì)象或類使用synchronized關(guān)鍵字即可
特點(diǎn)
功能 synchronized ReentrantLock
鎖獲取超時(shí) 不支持 支持
獲取鎖響應(yīng)中斷 不支持 支持
是否要手動(dòng)釋放鎖
性能
  1. 早期的synchronized是十分低下的想鹰,在1.5之后引入了偏向鎖,輕量級(jí)鎖药版,重量級(jí)鎖辑舷,同時(shí)也優(yōu)化了鎖的爭(zhēng)搶流程,大大提高 了synchronized的性能槽片,同時(shí)在后面的版本中也還在對(duì)synchronized有優(yōu)化何缓。后面在JDK1.8中,ConcurrentHashMap取消了ReentrantLock的設(shè)計(jì)还栓,而是直接用synchronized碌廓,因?yàn)閟ynchronized在數(shù)據(jù)量少時(shí)性能足夠優(yōu)秀,而且整個(gè)流程更加簡(jiǎn)單蝙云,具體可以參考談?wù)凜oncurrentHashMap1.7和1.8的不同實(shí)現(xiàn)
  2. ReentrantLock沒有像synchronized那樣對(duì)鎖劃分成多個(gè)等級(jí)氓皱,根據(jù)并發(fā)程度的不同采取不同的策略,由于synchronized在競(jìng)爭(zhēng)激烈的情況下會(huì)做鎖的升級(jí)勃刨,性能急劇下降波材。所以ReentrantLock在競(jìng)爭(zhēng)比較激烈的時(shí)候性能是很穩(wěn)定的,少量競(jìng)爭(zhēng)或者單線程的時(shí)候可能會(huì)遜色一些身隐。

源碼分析

  1. ReentrantLock實(shí)現(xiàn)Lock接口嘹害。Lock 實(shí)現(xiàn)提供了比使用 synchronized 方法和語(yǔ)句可獲得的更廣泛的鎖定操作,此實(shí)現(xiàn)允許更靈活的結(jié)構(gòu)喷户,可以具有差別很大的屬性始绍,可以支持多個(gè)相關(guān)的 Condition
    對(duì)象。
  2. ReentrantLock內(nèi)部是由Sync垢揩,NonfairSync(非公平同步)玖绿,F(xiàn)airSync(公平同步)三個(gè)內(nèi)部類實(shí)現(xiàn),且這三個(gè)內(nèi)部類都實(shí)現(xiàn)繼承自非常著名的AbstractQueuedSynchronizer叁巨,簡(jiǎn)寫AQS斑匪,中文名叫隊(duì)列同步器,了解這個(gè)隊(duì)列同步器锋勺,基本上就完成對(duì)整個(gè)concurrent包下的鎖完全了解了蚀瘸。很多人不了解為何設(shè)計(jì)成內(nèi)部類狡蝶,我記得有一句話講得好,內(nèi)部類就像人體的一個(gè)器官贮勃,它自己有完整的功能贪惹,也能給身體提供功能,如果不把它設(shè)計(jì)成一個(gè)內(nèi)部類就會(huì)很凌亂寂嘉。
  3. 默認(rèn)構(gòu)造成NonfairSync奏瞬。源碼如下
    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
  1. 調(diào)用Lock方法,我們先從默認(rèn)非公平鎖開始分析
  /**
    * Performs lock.  Try immediate barge, backing up to normal
    * acquire on failure.
    */
    final void lock() {
         if (compareAndSetState(0, 1))
             // 設(shè)置當(dāng)前擁有獨(dú)占訪問(wèn)的線程垫释。
             setExclusiveOwnerThread(Thread.currentThread());
         else
             acquire(1);
    }

代碼一開始就直接用compareAndSetState丝格,這是典型的CAS操作,關(guān)于CAS操作可以參考java基礎(chǔ):CAS操作棵譬,然后就是設(shè)置當(dāng)前擁有獨(dú)占訪問(wèn)的線程显蝌,否則執(zhí)行acquire(1)
非公平鎖的acquire(1)實(shí)現(xiàn)

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire的底層實(shí)現(xiàn)

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

意思就是如果沒有,就設(shè)置當(dāng)前擁有獨(dú)占訪問(wèn)的線程订咸,設(shè)置完后就new一個(gè)Node節(jié)點(diǎn)添加到AQS隊(duì)列中去等曼尊,等獲得到空閑時(shí)就會(huì)設(shè)置當(dāng)前擁有獨(dú)自訪問(wèn)的線程,完事后就調(diào)用Thread.currentThread().interrupt();中斷線程一直去嘗試獲取脏嚷。

  1. 公平鎖Lock實(shí)現(xiàn)
        final void lock() {
            acquire(1);
        }

我們發(fā)現(xiàn)公平鎖直接就調(diào)用acquire(1)方法骆撇,與非公平鎖不同的是缺少CAS操作。

  1. lock.unLock();實(shí)現(xiàn)
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

不管是公平鎖還是非公平鎖父叙,unLock實(shí)現(xiàn)都一樣神郊,基本上就是設(shè)置一些狀態(tài),然后把獨(dú)占線程清空趾唱。

什么時(shí)候選擇用 ReentrantLock 代替 synchronized

在早期的版本中涌乳,synchronized性能其實(shí)非常底的,但在1.8之后甜癞,對(duì)synchronized的優(yōu)化已經(jīng)足夠好了夕晓,甚至一些情況下,synchronized性能比ReentrantLock還要好悠咱,所以在1.8版本的ConcurrentHashMap實(shí)現(xiàn)取消了ReetrantLock設(shè)計(jì)蒸辆,直接用synchronized。這并不代表ReetrantLock就不行了析既,在高并發(fā)及多線程環(huán)境下躬贡,ReetrantLock的性能依舊是最優(yōu)選擇。

總結(jié)

了解ReentrantLock其實(shí)最主要的兩點(diǎn)眼坏,一是CAS操作拂玻,二是AQS,當(dāng)然AQS是一切并發(fā)包下基礎(chǔ),我們只有了解其中的原理纺讲,就很容易了解這些鎖的調(diào)用關(guān)系,其實(shí)都是AQS的一些應(yīng)用囤屹,比如公平鎖與非公平鎖的區(qū)別就是是否獲得鎖時(shí)是否有CAS操作熬甚,有的是非公平鎖,沒有的是公平鎖肋坚。因?yàn)镽eentrantLock是對(duì)像乡括,synchronized的一些用法都可以用ReentrantLock實(shí)現(xiàn),而一些時(shí)間鎖等候智厌、可中斷鎖等候诲泌、無(wú)塊結(jié)構(gòu)鎖、多個(gè)條件變量或者鎖投票是synchronized不具備的铣鹏,而ReentrantLock的性能在多線程環(huán)境中優(yōu)勢(shì)相當(dāng)明顯敷扫,所以理解ReentrantLock是對(duì)多線程和大并發(fā)環(huán)境的編程多了一種技術(shù)選型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诚卸,一起剝皮案震驚了整個(gè)濱河市葵第,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌合溺,老刑警劉巖卒密,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異棠赛,居然都是意外死亡哮奇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門睛约,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鼎俘,“玉大人,你說(shuō)我怎么就攤上這事痰腮《妫” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵膀值,是天一觀的道長(zhǎng)棍丐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)沧踏,這世上最難降的妖魔是什么歌逢? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮翘狱,結(jié)果婚禮上秘案,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好阱高,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布赚导。 她就那樣靜靜地躺著,像睡著了一般赤惊。 火紅的嫁衣襯著肌膚如雪吼旧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天未舟,我揣著相機(jī)與錄音圈暗,去河邊找鬼。 笑死裕膀,一個(gè)胖子當(dāng)著我的面吹牛员串,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昼扛,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼寸齐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了抄谐?” 一聲冷哼從身側(cè)響起访忿,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎斯稳,沒想到半個(gè)月后海铆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挣惰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年卧斟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憎茂。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡珍语,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竖幔,到底是詐尸還是另有隱情板乙,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布拳氢,位于F島的核電站募逞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏馋评。R本人自食惡果不足惜放接,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望留特。 院中可真熱鬧纠脾,春花似錦玛瘸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至慧脱,卻和暖如春再来,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背磷瘤。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搜变,地道東北人采缚。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像挠他,于是被迫代替她去往敵國(guó)和親扳抽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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