Java的各種鎖

不管是工作還是跳槽面試,鎖這個問題始終避不開蒂破,而且極易成為絆腳石窥妇,前幾天看到一些比較好的文檔符匾,于是就搬過來做個筆記

  1. 鎖的分類
    • 自旋鎖:自旋,jvm默認(rèn)是10次吧,有jvm自己控制。for去爭取鎖
    • 阻塞鎖:被阻塞的線程,不會爭奪鎖魂那。
    • 可重入鎖:多次進(jìn)入改鎖的域
    • 讀寫鎖:
    • 互斥鎖:鎖本身就是互斥的
    • 悲觀鎖:不相信,這里會是安全的稠项,必須全部上鎖
    • 樂觀鎖:相信涯雅,這里是安全的。
    • 公平鎖:有優(yōu)先級的鎖
    • 非公平鎖:無優(yōu)先級的鎖
    • 偏向鎖:無競爭不鎖展运,有競爭掛起活逆,轉(zhuǎn)為輕量鎖
    • 對象鎖:鎖住對象
    • 線程鎖:
    • 鎖粗化:多鎖變成一個,自己處理
    • 輕量級鎖:CAS 實現(xiàn)
    • 鎖消除:偏向鎖就是鎖消除的一種
    • 鎖膨脹:jvm實現(xiàn)拗胜,鎖粗化
    • 信號量:使用阻塞鎖 實現(xiàn)的一種策略
    • 排它鎖:X鎖蔗候,若事務(wù)T對數(shù)據(jù)對象A加上X鎖,則只允許T讀取和修改A埂软,其他任何事務(wù)都不能再對A加任何類型的鎖锈遥,直到T釋放A上的鎖。這就保證了其他事務(wù)在T釋放A上的鎖之前不能再讀取和修改A勘畔。
  1. 自旋鎖
    • 自旋鎖是采用讓當(dāng)前線程不停地的在循環(huán)體內(nèi)執(zhí)行實現(xiàn)的所灸,當(dāng)循環(huán)的條件被其他線程改變時 才能進(jìn)入臨界區(qū)
      // 注:該例子為非公平鎖,獲得鎖的先后順序炫七,不會按照進(jìn)入lock的先后順序進(jìn)行爬立。
      public class SpinLock {
          private AtomicReference<Thread> sign =new AtomicReference<>();
          public void lock(){
              Thread current = Thread.currentThread();
              while(!sign .compareAndSet(null, current)){  }
          }
          public void unlock (){
              Thread current = Thread.currentThread();
              sign .compareAndSet(current, null);
          }
      }
      
      • 使用了CAS原子操作,lock函數(shù)將owner設(shè)置為當(dāng)前線程万哪,并且預(yù)測原來的值為空侠驯。unlock函數(shù)將owner設(shè)置為null,并且預(yù)測值為當(dāng)前線程壤圃。當(dāng)有第二個線程調(diào)用lock操作時由于owner值不為空陵霉,導(dǎo)致循環(huán)一直被執(zhí)行,直至第一個線程調(diào)用unlock函數(shù)將owner設(shè)置為null伍绳,第二個線程才能進(jìn)入臨界區(qū)。由于自旋鎖只是將當(dāng)前線程不停地執(zhí)行循環(huán)體乍桂,不進(jìn)行線程狀態(tài)的改變冲杀,所以響應(yīng)速度更快效床。但當(dāng)線程數(shù)不停增加時,性能下降明顯权谁,因為每個線程都需要執(zhí)行剩檀,占用CPU時間。如果線程競爭不激烈旺芽,并且保持鎖的時間段沪猴。適合使用自旋鎖。
    • 自旋鎖還有三種常見的鎖形式:TicketLock 采章,CLHlock 和MCSlock
      • Ticket鎖主要解決的是訪問順序的問題运嗜,主要的問題是在多核CPU上,代碼如下悯舟,每次都要查詢一個serviceNum 服務(wù)號担租,影響性能(必須要到主內(nèi)存讀取,并阻止其他CPU修改)抵怎。
        import java.util.concurrent.atomic.AtomicInteger;        
        public class TicketLock {
            private AtomicInteger serviceNum = new AtomicInteger();
            private AtomicInteger ticketNum  = new AtomicInteger();
            private static final ThreadLocal<Integer> local = new ThreadLocal<Integer>();
            public void lock() {
                int myticket = ticketNum.getAndIncrement();
                local.set(myticket);
                while (myticket != serviceNum.get()) {}
            }
            public void unlock() {
                int myticket = local.get();
                serviceNum.compareAndSet(myticket, myticket + 1);
            }
        }
        
      • CLHLock:Craig, Landin, and Hagersten Locks奋救,是一個自旋鎖,能確保無饑餓性反惕,提供先來先服務(wù)的公平性尝艘;CLH鎖也是一種基于鏈表的可擴(kuò)展、高性能姿染、公平的自旋鎖背亥,申請線程只在本地變量上自旋,它不斷輪詢前驅(qū)的狀態(tài)盔粹,如果發(fā)現(xiàn)前驅(qū)釋放了鎖就結(jié)束自旋隘梨;
        • 當(dāng)一個線程需要獲取鎖時:
          1. 創(chuàng)建一個的CLHNode,將其中的locked設(shè)置為true表示需要獲取鎖舷嗡;
          2. 線程對tail域調(diào)用getAndSet方法轴猎,使自己加入到隊列的尾部,同時獲取一個指向其前趨結(jié)點(diǎn)的引用preNode进萄;
          3. 該線程就在前趨結(jié)點(diǎn)的locked字段上旋轉(zhuǎn)捻脖,直到前趨結(jié)點(diǎn)釋放鎖;
          4. 當(dāng)一個線程需要釋放鎖時中鼠,將當(dāng)前結(jié)點(diǎn)的locked域設(shè)置為false可婶,同時回收前趨結(jié)點(diǎn);
        • 示例代碼如下:
        import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;        
        public class CLHLock {
            public static class CLHNode {
                private volatile boolean isLocked = true;
            }
            private volatile CLHNode tail;
            private static final ThreadLocal<CLHNode> local = new ThreadLocal<CLHNode>();
            private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> updater = 
                    AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, CLHNode.class, "tail");        
            public void lock() {
                CLHNode node = new CLHNode();
                local.set(node);
                CLHNode preNode = updater.getAndSet(this, node);
                if (preNode != null) {
                    while (preNode.isLocked) {}
                    preNode = null;
                    local.set(node);
                }
            }        
            public void unlock() {
                CLHNode node = local.get();
                if (!updater.compareAndSet(this, node, null)) {
                    node.isLocked = false;
                }
                node = null;
            }
        }
        
      • MCSLock則是對本地變量的節(jié)點(diǎn)進(jìn)行循環(huán)援雇。MSC與CLH最大的不同并不是鏈表是顯示還是隱式矛渴,而是線程自旋的規(guī)則不同:CLH是在前趨結(jié)點(diǎn)的locked域上自旋等待,而MCS是在自己的結(jié)點(diǎn)的locked域上自旋等待。正因為如此具温,它解決了CLH在NUMA系統(tǒng)架構(gòu)中獲取locked域狀態(tài)內(nèi)存過遠(yuǎn)的問題蚕涤;不存在CLHlock 的問題。
        import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
        public class MCSLock {
            public static class MCSNode {
                volatile MCSNode next;
                volatile boolean locked = true;
            }
            private static final ThreadLocal<MCSNode> node = new ThreadLocal<MCSNode>();
            private volatile MCSNode queue;
            private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> updater = 
                    AtomicReferenceFieldUpdater.newUpdater(MCSLock.class, MCSNode.class, "queue");
            public void lock() {
                MCSNode currentNode = new MCSNode();
                node.set(currentNode);
                MCSNode preNode = updater.getAndSet(this, currentNode);
                if (preNode != null) {
                    preNode.next = currentNode;
                    while (currentNode.locked) {}
                }
            }
            public void unlock() {
                MCSNode currentNode = node.get();
                if (currentNode.next == null) {
                    if (updater.compareAndSet(this, currentNode, null)) {        
                    } else {
                        while (currentNode.next == null) {}
                    }
                } else {
                    currentNode.next.locked = false;
                    currentNode.next = null;
                }
            }
        }
        
    • 自旋鎖總結(jié)
      • 從代碼上 看铣猩,CLH 要比 MCS 更簡單揖铜;
      • CLH 的隊列是隱式的隊列,沒有真實的后繼結(jié)點(diǎn)屬性达皿;
      • MCS 的隊列是顯式的隊列天吓,有真實的后繼結(jié)點(diǎn)屬性;
      • JUC ReentrantLock 默認(rèn)內(nèi)部使用的鎖 即是 CLH鎖(有很多改進(jìn)的地方峦椰,將自旋鎖換成了阻塞鎖等等)龄寞;
  2. 阻塞鎖
    • 與自旋鎖不同,改變了線程的運(yùn)行狀態(tài)们何。在JAVA環(huán)境中萄焦,線程Thread有如下幾個狀態(tài):1,新建狀態(tài)冤竹;2拂封,就緒狀態(tài);3鹦蠕,運(yùn)行狀態(tài)冒签;4,阻塞狀態(tài)钟病;5萧恕,死亡狀態(tài)
    • 阻塞鎖,可以說是讓線程進(jìn)入阻塞狀態(tài)進(jìn)行等待肠阱,當(dāng)獲得相應(yīng)的信號(喚醒票唆,時間) 時,才可以進(jìn)入線程的準(zhǔn)備就緒狀態(tài)屹徘,準(zhǔn)備就緒狀態(tài)的所有線程走趋,通過競爭,進(jìn)入運(yùn)行狀態(tài)噪伊。
    • JAVA中簿煌,能夠進(jìn)入\退出、阻塞狀態(tài)或包含阻塞鎖的方法有 鉴吹,synchronized 關(guān)鍵字(其中的重量鎖)姨伟,ReentrantLock,Object.wait()\notify()豆励,LockSupport.park()/unpart()(JUC經(jīng)常使用)
    • 下面是一個JAVA 阻塞鎖實例
      import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
      import java.util.concurrent.locks.LockSupport;
      public class CLHLock1 {
          public static class CLHNode {
              private volatile Thread locked;
          }
          private volatile CLHNode tail;
          private static final ThreadLocal<CLHNode> local   = new ThreadLocal<CLHNode>();
          private static final AtomicReferenceFieldUpdater<CLHLock1, CLHNode> updater = 
                    AtomicReferenceFieldUpdater.newUpdater(CLHLock1.class, CLHNode.class, "tail");
          public void lock() {
              CLHNode node = new CLHNode();
              local.set(node);
              CLHNode preNode = updater.getAndSet(this, node);
              if (preNode != null) {
                  preNode.locked = Thread.currentThread();
                  LockSupport.park(this);
                  preNode = null;
                  local.set(node);
              }
          }
          public void unlock() {
              CLHNode node = local.get();
              if (!updater.compareAndSet(this, node, null)) {
                  System.out.println("unlock\t" + node.locked.getName());
                  LockSupport.unpark(node.locked);
              }
              node = null;
          }
      }
      
      • 在這里我們使用了LockSupport.unpark()的阻塞鎖夺荒。 該例子是將CLH鎖修改而成。阻塞鎖的優(yōu)勢在于,阻塞的線程不會占用cpu時間般堆, 不會導(dǎo)致 CPu占用率過高在孝,但進(jìn)入時間以及恢復(fù)時間都要比自旋鎖略慢诚啃。在競爭激烈的情況下 阻塞鎖的性能要明顯高于 自旋鎖淮摔。理想的情況則是; 在線程競爭不激烈的情況下,使用自旋鎖始赎,競爭激烈的情況下使用和橙,阻塞鎖。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末造垛,一起剝皮案震驚了整個濱河市魔招,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌五辽,老刑警劉巖办斑,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異杆逗,居然都是意外死亡乡翅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門罪郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蠕蚜,“玉大人,你說我怎么就攤上這事悔橄“欣郏” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵癣疟,是天一觀的道長挣柬。 經(jīng)常有香客問我,道長睛挚,這世上最難降的妖魔是什么邪蛔? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮竞川,結(jié)果婚禮上店溢,老公的妹妹穿的比我還像新娘。我一直安慰自己委乌,他們只是感情好床牧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遭贸,像睡著了一般戈咳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天著蛙,我揣著相機(jī)與錄音删铃,去河邊找鬼。 笑死踏堡,一個胖子當(dāng)著我的面吹牛猎唁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播顷蟆,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诫隅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了帐偎?” 一聲冷哼從身側(cè)響起逐纬,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎削樊,沒想到半個月后豁生,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漫贞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年甸箱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绕辖。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡摇肌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仪际,到底是詐尸還是另有隱情围小,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布树碱,位于F島的核電站肯适,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏成榜。R本人自食惡果不足惜框舔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赎婚。 院中可真熱鬧刘绣,春花似錦、人聲如沸挣输。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撩嚼。三九已至停士,卻和暖如春挖帘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恋技。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工拇舀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜻底。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓骄崩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親朱躺。 傳聞我的和親對象是個殘疾皇子刁赖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354