不管是工作還是跳槽面試,鎖這個問題始終避不開蒂破,而且極易成為絆腳石窥妇,前幾天看到一些比較好的文檔符匾,于是就搬過來做個筆記
-
鎖的分類
- 自旋鎖:自旋,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勘畔。
-
自旋鎖
- 自旋鎖是采用讓當(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)一個線程需要獲取鎖時:
- 創(chuàng)建一個的CLHNode,將其中的locked設(shè)置為true表示需要獲取鎖舷嗡;
- 線程對tail域調(diào)用getAndSet方法轴猎,使自己加入到隊列的尾部,同時獲取一個指向其前趨結(jié)點(diǎn)的引用preNode进萄;
- 該線程就在前趨結(jié)點(diǎn)的locked字段上旋轉(zhuǎn)捻脖,直到前趨結(jié)點(diǎn)釋放鎖;
- 當(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; } }
- 當(dāng)一個線程需要獲取鎖時:
- 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; } } }
- Ticket鎖主要解決的是訪問順序的問題运嗜,主要的問題是在多核CPU上,代碼如下悯舟,每次都要查詢一個serviceNum 服務(wù)號担租,影響性能(必須要到主內(nèi)存讀取,并阻止其他CPU修改)抵怎。
- 自旋鎖總結(jié)
- 從代碼上 看铣猩,CLH 要比 MCS 更簡單揖铜;
- CLH 的隊列是隱式的隊列,沒有真實的后繼結(jié)點(diǎn)屬性达皿;
- MCS 的隊列是顯式的隊列天吓,有真實的后繼結(jié)點(diǎn)屬性;
- JUC ReentrantLock 默認(rèn)內(nèi)部使用的鎖 即是 CLH鎖(有很多改進(jìn)的地方峦椰,將自旋鎖換成了阻塞鎖等等)龄寞;
- 自旋鎖是采用讓當(dāng)前線程不停地的在循環(huán)體內(nèi)執(zhí)行實現(xiàn)的所灸,當(dāng)循環(huán)的條件被其他線程改變時 才能進(jìn)入臨界區(qū)
- 阻塞鎖
- 與自旋鎖不同,改變了線程的運(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ù)時間都要比自旋鎖略慢诚啃。在競爭激烈的情況下 阻塞鎖的性能要明顯高于 自旋鎖淮摔。理想的情況則是; 在線程競爭不激烈的情況下,使用自旋鎖始赎,競爭激烈的情況下使用和橙,阻塞鎖。