在之前的文章中,我們介紹了讀寫鎖,學(xué)習(xí)完之后你應(yīng)該已經(jīng)知道了讀寫鎖允許多個線程同時(shí)訪問共享變量,適用于讀多寫少的場景。那么在讀多寫少的場景中還有沒有更快的技術(shù)方案呢?還真有,在Java1.8這個版本里提供了一種叫StampedLock的鎖,它的性能就比讀寫鎖還要好。
下面我們介就來介紹一下StampedLock的使用方法、內(nèi)部工作原理以及在使用過程中需要注意的事項(xiàng)。
StampedLock支持的三種模式
我們先來看看StampedLock在使用什么,和上篇文章中的ReadWriteLock所有哪些區(qū)別。
ReadWriteLock支持兩種模式,一種是讀鎖,一種是寫鎖,而StampedLock支持三種模式,分別是寫鎖、悲觀鎖鎖、樂觀讀,其中寫鎖、悲觀讀鎖的語義和ReadWriteLock的寫鎖、讀鎖的語義非常類似。允許多個線程同時(shí)獲取悲觀讀鎖,但是只允許一個線程獲取寫鎖,寫鎖和悲觀讀鎖都是互斥的,然而不同的是里面的寫鎖和悲觀讀鎖加鎖成功之后,都會返回一個stamp。然后解鎖的時(shí)候需要傳入這個stamp。相關(guān)的實(shí)例代碼如下。
final StampedLock sl = new StampedLock();
//獲取/釋放悲觀讀鎖示意代碼
long stamp = sl.reaLock();
try{
//省略業(yè)務(wù)代碼
} finally {
sl.unlockRead(stamp);
}
//獲取/釋放寫鎖示意代碼
long stamp = sl.writeLock();
try{
//省略相關(guān)業(yè)務(wù)代碼
} finally {
sl.unlockWrite(stamp);
}
StampedLock 的性能之所以比ReadWriteLock還要好,關(guān)鍵是StampedLock支持樂觀讀的方式。ReadWriteLock支持多個線程同時(shí)讀,但是當(dāng)多個線程同時(shí)讀的時(shí)候,所有的寫操作也會被阻塞。而StampedLock提供的樂觀讀是允許一個線程獲取寫鎖的,也就是說不是所有寫操作都被阻塞的。
注意,這里我們用的是"樂觀讀"這個詞,而不是樂觀讀鎖,是要提醒你,樂觀讀操作是無鎖的,所以相比較ReadWriteLock的讀鎖,樂觀讀的性能更好一點(diǎn)。文中下面這段代碼是出自于Java SDK官方示例,并略作修改。在distanceFromRrigin()這個方法中,首先通過調(diào)用tryOptimisticRead獲得了一個stamp。這里的tryOptimisticRead就是我們前面提到的樂觀讀。之后將共享變量X和Y讀入方法的局部變量中。不過需要注意的是,由于tryOptimisticRead是無鎖的,所以共享變量X和Y讀入方法局部變量時(shí),X和Y有可能被其他線程修改了,因此最后讀完之后還需要再次驗(yàn)證一下是否存在寫操作,這個操作是通過調(diào)用validate (stamp)來實(shí)現(xiàn)的。
class Point {
private int x,y;
final StampedLock sl = new StampedLock();
//計(jì)算到原點(diǎn)的距離
int distanceFromOrigin(){
//樂觀讀
long stamp = sl.tryOptimisticRead();
//讀入局部變量
//讀的過程中數(shù)據(jù)可能被修改
int curX = x,curY = y;
//判斷執(zhí)行讀操作期間,是否存在寫操作
//如果存在,返回false
if(!sl.validate(stamp)){
//升級為悲觀鎖
stamp = sl.readLock();
try{
curX = x;
curY = y;
} finally {
sl.unLockRead(stamp);
}
}
return Math.sqrt(curX * curX + curY * curY);
}
}
在上面這個代碼示例中,如果執(zhí)行樂觀讀操作期間存在寫操作,會把樂觀讀升級為悲觀讀鎖。這個做法挺合理的,否則你就需要在一個循環(huán)里反復(fù)執(zhí)行樂觀讀,直到執(zhí)行樂觀讀操作期間沒有寫操作,只有這樣才能保證X和Y的正確性和一致性。而循環(huán)讀會浪費(fèi)大量的CPU。升級為悲觀讀鎖代碼簡練且不易出錯,建議你在具體實(shí)踐的時(shí)候也采用這樣的方法。
進(jìn)一步理解樂觀讀
如果你曾經(jīng)用過數(shù)據(jù)庫的樂觀鎖,你可能會發(fā)現(xiàn)StampLock的樂觀讀和數(shù)據(jù)庫的樂觀讀鎖有異曲同工之妙。的確是這樣的,就我個人而言,我是先接觸數(shù)據(jù)庫的樂觀鎖,然后再接觸的StampLock,我就覺得我前期數(shù)據(jù)庫里的樂觀鎖的學(xué)習(xí),對于后面的理解StampLock的樂觀讀有很大的幫助,所以這里有必要再介紹一下數(shù)據(jù)庫里的樂觀鎖。
還記得我第一次使用數(shù)據(jù)庫樂觀鎖的場景是這樣的,在ERP的生產(chǎn)模塊里,會有多個人通過ERP系統(tǒng)提供的UI同時(shí)修改同一條生產(chǎn)訂單,那如何保證生產(chǎn)訂單數(shù)據(jù)是并發(fā)安全的呢?我采用的方案就是樂觀鎖。
樂觀鎖的實(shí)現(xiàn)很簡單,在生產(chǎn)訂單的表product_doc里面增加一個數(shù)字型的版本號字段version,每次更新product_doc這個表的時(shí)候,都將version字段加1。生產(chǎn)訂單的UI在展示的時(shí)候需要查詢數(shù)據(jù)庫。此時(shí)將這個version字段和其他業(yè)務(wù)字段一起返回給生產(chǎn)訂單UI。假設(shè)用戶查詢的生產(chǎn)訂單的ID=777,那么SQL語句類似于下面這樣。
select id,....,version
from product_doc
where id = 777
用戶在生產(chǎn)訂單UI執(zhí)行保存操作時(shí)候,后臺利用下面的SQL語句更新生產(chǎn)訂單,此時(shí)我們假設(shè)該條生產(chǎn)訂單的version等于9。
update product_doc
set version=version+1
where id=777 and versoin=9
如果這條語SQL語句成執(zhí)行成功,并且返回的條數(shù)等于1,那么說明從生產(chǎn)訂單UI執(zhí)行查詢操作到執(zhí)行保存操作期間沒有其他人修改過這條數(shù)據(jù)。因?yàn)槿绻@期間其他人修改過這條數(shù)據(jù),那么版本號一定會大于9。
你會發(fā)現(xiàn)數(shù)據(jù)庫里的樂觀鎖查詢的時(shí)候需要把version字段查出來,更新的時(shí)候要利用version字段做校驗(yàn),這個version字段就類似于StampLock里面的stamp,這樣對比著看,你相信你會更容易理解StampLock里面樂觀讀的用法。
StampLock使用注意事項(xiàng)
對于讀多寫少的場景StampLock性能很好,簡單的應(yīng)用場景基本上可以替代ReadWriteLock,但是StampLock的功能僅僅是ReadWriteLock的子集,在使用的時(shí)候還是有幾個需地方需要注意一下。
StampLock在命名上并沒有增加Reentrant,想必你已經(jīng)猜到了,StampLock應(yīng)該是不可重入的,事實(shí)上的確是這樣的,StampLock不支持重入,這個是在使用中必須要注意的。
另外StampLock的悲觀讀鎖、寫鎖都不支持條件變量,這個你也需要注意。
還有一點(diǎn)需要特別注意,那就是如果線程阻塞在StampLock的readLock()上時(shí),此時(shí)調(diào)用該阻塞線程的interrupt()方法會導(dǎo)致CPU飆升。
所以使用StampLock一定不要調(diào)用中斷操作,如果需要支持中斷功能,一定使用可中斷的悲觀讀鎖readLockInterruptibly()和寫鎖writeLockInterruptibly(),這個規(guī)則一定要記清楚。文章來源:http://www.zghlxwxcb.cn/news/detail-656864.html
總結(jié)
StampLock的使用看上去有點(diǎn)復(fù)雜,但是如果你能理解樂觀所背后的原理,使用起來還是比較流暢的。文章來源地址http://www.zghlxwxcb.cn/news/detail-656864.html
到了這里,關(guān)于有沒有比讀寫鎖更快的鎖的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!