-
鎖的分類
- 自旋鎖:自旋,jvm默認是10次吧,有jvm自己控制。for去爭取鎖
- 阻塞鎖:被阻塞的線程,不會爭奪鎖。
- 可重入鎖:多次進入改鎖的域
- 讀寫鎖:
- 互斥鎖:鎖本身就是互斥的
- 悲觀鎖:不相信,這里會是安全的,必須全部上鎖
- 樂觀鎖:相信,這里是安全的。
- 公平鎖:有優(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)的條件被其他線程改變時 才能進入臨界區(qū)
// 注:該例子為非公平鎖,獲得鎖的先后順序,不會按照進入lock的先后順序進行。 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,第二個線程才能進入臨界區(qū)。由于自旋鎖只是將當(dāng)前線程不停地執(zhí)行循環(huá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鎖也是一種基于鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,它不斷輪詢前驅(qū)的狀態(tài),如果發(fā)現(xiàn)前驅(qū)釋放了鎖就結(jié)束自旋;
- 當(dāng)一個線程需要獲取鎖時:
- 創(chuàng)建一個的CLHNode,將其中的locked設(shè)置為true表示需要獲取鎖;
- 線程對tail域調(diào)用getAndSet方法,使自己加入到隊列的尾部,同時獲取一個指向其前趨結(jié)點的引用preNode;
- 該線程就在前趨結(jié)點的locked字段上旋轉(zhuǎn),直到前趨結(jié)點釋放鎖;
- 當(dāng)一個線程需要釋放鎖時,將當(dāng)前結(jié)點的locked域設(shè)置為false,同時回收前趨結(jié)點;
- 示例代碼如下:
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é)點進行循環(huán)。MSC與CLH最大的不同并不是鏈表是顯示還是隱式,而是線程自旋的規(guī)則不同:CLH是在前趨結(jié)點的locked域上自旋等待,而MCS是在自己的結(jié)點的locked域上自旋等待。正因為如此,它解決了CLH在NUMA系統(tǒng)架構(gòu)中獲取locked域狀態(tài)內(nèi)存過遠的問題;不存在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é)點屬性;
- MCS 的隊列是顯式的隊列,有真實的后繼結(jié)點屬性;
- JUC ReentrantLock 默認內(nèi)部使用的鎖 即是 CLH鎖(有很多改進的地方,將自旋鎖換成了阻塞鎖等等);
- 自旋鎖是采用讓當(dāng)前線程不停地的在循環(huán)體內(nèi)執(zhí)行實現(xiàn)的,當(dāng)循環(huán)的條件被其他線程改變時 才能進入臨界區(qū)
- 阻塞鎖
- 與自旋鎖不同,改變了線程的運行狀態(tài)。在JAVA環(huán)境中,線程Thread有如下幾個狀態(tài):1,新建狀態(tài);2,就緒狀態(tài);3,運行狀態(tài);4,阻塞狀態(tài);5,死亡狀態(tài)
- 阻塞鎖,可以說是讓線程進入阻塞狀態(tài)進行等待,當(dāng)獲得相應(yīng)的信號(喚醒,時間) 時,才可以進入線程的準備就緒狀態(tài),準備就緒狀態(tài)的所有線程,通過競爭,進入運行狀態(tài)。
- JAVA中,能夠進入\退出、阻塞狀態(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占用率過高,但進入時間以及恢復(fù)時間都要比自旋鎖略慢。在競爭激烈的情況下 阻塞鎖的性能要明顯高于 自旋鎖。理想的情況則是; 在線程競爭不激烈的情況下,使用自旋鎖,競爭激烈的情況下使用,阻塞鎖。