鎖作為并發(fā)共享數(shù)據(jù),保證一致性的工具,在java平臺(tái)有多種實(shí)現(xiàn)(如 synchronized 和 ReentrantLock等等 ) 。java平臺(tái)下的鎖如下所示:
1、自旋鎖2、自旋鎖的其他種類3、阻塞鎖4、可重入鎖5、讀寫鎖6、互斥鎖7、悲觀鎖8、樂觀鎖9、公平鎖10、非公平鎖11、偏向鎖12、對(duì)象鎖13、線程鎖14、鎖粗化15、輕量級(jí)鎖16、鎖消除17、鎖膨脹18、信號(hào)量
自旋鎖是采用讓當(dāng)前線程不停地的在循環(huán)體內(nèi)執(zhí)行實(shí)現(xiàn)的,當(dāng)循環(huán)的條件被其他線程改變時(shí) 才能進(jìn)入臨界區(qū)。如下
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ù)測(cè)原來的值為空。unlock函數(shù)將owner設(shè)置為null,并且預(yù)測(cè)值為當(dāng)前線程。
當(dāng)有第二個(gè)線程調(diào)用lock操作時(shí)由于owner值不為空,導(dǎo)致循環(huán)一直被執(zhí)行,直至第一個(gè)線程調(diào)用unlock函數(shù)將owner設(shè)置為null,第二個(gè)線程才能進(jìn)入臨界區(qū)。
由于自旋鎖只是將當(dāng)前線程不停地執(zhí)行循環(huán)體,不進(jìn)行線程狀態(tài)的改變,所以響應(yīng)速度更快。但當(dāng)線程數(shù)不停增加時(shí),性能下降明顯,因?yàn)槊總€(gè)線程都需要執(zhí)行,占用CPU時(shí)間。如果線程競(jìng)爭(zhēng)不激烈,并且保持鎖的時(shí)間段。適合使用自旋鎖。
注:該例子為非公平鎖,獲得鎖的先后順序,不會(huì)按照進(jìn)入lock的先后順序進(jìn)行。
2.自旋鎖的其他種類
在自旋鎖中 另有三種常見的鎖形式:TicketLock ,CLHlock 和MCSlock
Ticket鎖主要解決的是訪問順序的問題,主要的問題是在多核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); }}每次都要查詢一個(gè)serviceNum 服務(wù)號(hào),影響性能(必須要到主內(nèi)存讀取,并阻止其他cpu修改)。CLHLock 和MCSLock 則是兩種類型相似的公平鎖,采用鏈表的形式進(jìn)行排序,
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;public class CLHLock { public static class CLHNode { private volatile boolean isLocked = true; } @SuppressWarnings("unused") 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; }}CLHlock是不停的查詢前驅(qū)變量, 導(dǎo)致不適合在NUMA 架構(gòu)下使用(在這種結(jié)構(gòu)下,每個(gè)線程分布在不同的物理內(nèi)存區(qū)域)
MCSLock則是對(duì)本地變量的節(jié)點(diǎn)進(jìn)行循環(huán)。不存在CLHlock 的問題。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;public class MCSLock { public static class MCSNode { volatile MCSNode next; volatile boolean isLocked = true; }private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>(); @SuppressWarnings("unused") 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.isLocked) { } } } 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.isLocked = false; currentNode.next = null; } }}從代碼上 看,CLH 要比 MCS 更簡(jiǎn)單,
CLH 的隊(duì)列是隱式的隊(duì)列,沒有真實(shí)的后繼結(jié)點(diǎn)屬性。
MCS 的隊(duì)列是顯式的隊(duì)列,有真實(shí)的后繼結(jié)點(diǎn)屬性。
JUC ReentrantLock 默認(rèn)內(nèi)部使用的鎖 即是 CLH鎖(有很多改進(jìn)的地方,將自旋鎖換成了阻塞鎖等等)。
三、阻塞鎖:
阻塞鎖,與自旋鎖不同,改變了線程的運(yùn)行狀態(tài)。在JAVA環(huán)境中,線程Thread有如下幾個(gè)狀態(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)的信號(hào)(喚醒,時(shí)間) 時(shí),才可以進(jìn)入線程的準(zhǔn)備就緒狀態(tài),準(zhǔn)備就緒狀態(tài)的所有線程,通過競(jìng)爭(zhēng),進(jìn)入運(yùn)行狀態(tài)。JAVA中,能夠進(jìn)入/退出、阻塞狀態(tài)或包含阻塞鎖的方法有 ,synchronized 關(guān)鍵字(其中的重量鎖),ReentrantLock,Object.wait()/notify(),LockSupport.park()/unpart()(j.u.c經(jīng)常使用)
下面是一個(gè)JAVA 阻塞鎖實(shí)例
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;import java.util.concurrent.locks.LockSupport;public class CLHLock1 { public static class CLHNode { private volatile Thread isLocked; } @SuppressWarnings("unused") 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.isLocked = 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.isLocked.getName()); LockSupport.unpark(node.isLocked); } node = null; }}在這里我們使用了LockSupport.unpark()的阻塞鎖。 該例子是將CLH鎖修改而成。
阻塞鎖的優(yōu)勢(shì)在于,阻塞的線程不會(huì)占用cpu時(shí)間, 不會(huì)導(dǎo)致 CPu占用率過高,但進(jìn)入時(shí)間以及恢復(fù)時(shí)間都要比自旋鎖略慢。
在競(jìng)爭(zhēng)激烈的情況下 阻塞鎖的性能要明顯高于 自旋鎖。
理想的情況則是; 在線程競(jìng)爭(zhēng)不激烈的情況下,使用自旋鎖,競(jìng)爭(zhēng)激烈的情況下使用,阻塞鎖。
本文里面講的是廣義上的可重入鎖,而不是單指JAVA下的ReentrantLock。
可重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數(shù)獲得鎖之后 ,內(nèi)層遞歸函數(shù)仍然有獲取該鎖的代碼,但不受影響。在JAVA環(huán)境下 ReentrantLock 和synchronized 都是 可重入鎖
下面是使用實(shí)例
public class Test implements Runnable{ public synchronized void get(){ System.out.println(Thread.currentThread().getId()); set(); } public synchronized void set(){ System.out.println(Thread.currentThread().getId()); } @Override public void run() { get(); } public static void main(String[] args) { Test ss=new Test(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); }} |
public class Test implements Runnable { ReentrantLock lock = new ReentrantLock(); public void get() { lock.lock(); System.out.println(Thread.currentThread().getId()); set(); lock.unlock(); } public void set() { lock.lock(); System.out.println(Thread.currentThread().getId()); lock.unlock(); } @Override public void run() { get(); } public static void main(String[] args) { Test ss = new Test(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); }}public class SpinLock {private AtomicReference<Thread> owner =new AtomicReference<>(); public void lock(){ Thread current = Thread.currentThread(); while(!owner.compareAndSet(null, current)){ } } public void unlock (){ Thread current = Thread.currentThread(); owner.compareAndSet(current, null); }}對(duì)于自旋鎖來說,1、若有同一線程兩調(diào)用lock() ,會(huì)導(dǎo)致第二次調(diào)用lock位置進(jìn)行自旋,產(chǎn)生了死鎖說明這個(gè)鎖并不是可重入的。(在lock函數(shù)內(nèi),應(yīng)驗(yàn)證線程是否為已經(jīng)獲得鎖的線程)2、若1問題已經(jīng)解決,當(dāng)unlock()第一次調(diào)用時(shí),就已經(jīng)將鎖釋放了。實(shí)際上不應(yīng)釋放鎖。(采用計(jì)數(shù)次進(jìn)行統(tǒng)計(jì))修改之后,如下:public class SpinLock1 { private AtomicReference<Thread> owner =new AtomicReference<>(); private int count =0; public void lock(){ Thread current = Thread.currentThread(); if(current==owner.get()) { count++; return ; } while(!owner.compareAndSet(null, current)){ }}public void unlock (){ Thread current = Thread.currentThread(); if(current==owner.get()){ if(count!=0){ count--; }else{ owner.compareAndSet(current, null); } }}該自旋鎖即為可重入鎖。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注