悲觀鎖(Pessimistic Lock) 顧名思義,就是 很悲觀 ,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)block直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,
顧名思義,就是很悲觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)block直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
我們認(rèn)為系統(tǒng)中的并發(fā)更新會(huì)非常頻繁,并且事務(wù)失敗了以后重來的開銷很大,這樣以來,我們就需要采用真正意義上的鎖來進(jìn)行實(shí)現(xiàn)。悲觀鎖的基本思想就是每次一個(gè)事務(wù)讀取某一條記錄后,就會(huì)把這條記錄鎖住,這樣其它的事務(wù)要想更新,必須等以前的事務(wù)提交或者回滾解除鎖。
大多在數(shù)據(jù)庫層面實(shí)現(xiàn)加鎖操作,JDBC方式:在JDBC中使用悲觀鎖,需要使用select for update
語句,e.g.
Select * from Account
where ...(where condition).. for update
顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫如果提供類似于write_condition機(jī)制的其實(shí)都是提供的樂觀鎖。
我們認(rèn)為系統(tǒng)中的事務(wù)并發(fā)更新不會(huì)很頻繁,即使沖突了也沒事,大不了重新再來一次。它的基本思想就是每次提交一個(gè)事務(wù)更新時(shí),我們想看看要修改的東西從上次讀取以后有沒有被其它事務(wù)修改過,如果修改過,那么更新就會(huì)失敗。
大多是基于數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn),何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個(gè) “version” 字段來實(shí)現(xiàn)。
讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提 交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù) 版本號(hào)大于數(shù)據(jù)庫表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過期數(shù)據(jù)。
假如系統(tǒng)中有一個(gè)Account的實(shí)體類,我們?cè)贏ccount中多加一個(gè)version字段,那么我們JDBC Sql語句將如下寫:
e.g.
Select a.version....from Account as a
where (where condition..)
Update Account set version = version+1.....(another field)
where version =?...(another contidition)
這樣以來我們就可以通過更新結(jié)果的行數(shù)來進(jìn)行判斷,如果更新結(jié)果的行數(shù)為0,那么說明實(shí)體從加載以來已經(jīng)被其它事務(wù)更改了,所以就拋出自定義的樂觀鎖定異常。具體實(shí)例如下:
int rowsUpdated = statement.executeUpdate(sql);
if (rowsUpdated ==0 ) {
throws new OptimisticLockingFailureException();
}
Synchronized互斥鎖屬于悲觀鎖,它有一個(gè)明顯的缺點(diǎn),它不管數(shù)據(jù)存不存在競(jìng)爭(zhēng)都加鎖,隨著并發(fā)量增加,且如果鎖的時(shí)間比較長(zhǎng),其性能開銷將會(huì)變得很大。有沒有辦法解決這個(gè)問題?答案就是基于沖突檢測(cè)的樂觀鎖。這種模式下,已經(jīng)沒有所謂的鎖概念了,每條線程都直接先去執(zhí)行操作,計(jì)算完成后檢測(cè)是否與其他線程存在共享數(shù)據(jù)競(jìng)爭(zhēng),如果沒有則讓此操作成功,如果存在共享數(shù)據(jù)競(jìng)爭(zhēng)則可能不斷地重新執(zhí)行操作和檢測(cè),直到成功為止,這種叫做CAS自旋。
以AtomicInteger的incrementAndGet的實(shí)現(xiàn)為例:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
首先可以看到他是通過一個(gè)無限循環(huán)(spin)直到increment成功為止。
循環(huán)的內(nèi)容是:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
直接調(diào)用的是UnSafe這個(gè)類的compareAndSwapInt方法,全稱是sun.misc.Unsafe。這個(gè)類是Oracle(Sun)提供的實(shí)現(xiàn),可能在別的公司的JDK里就不是這個(gè)類了。
/**
* Atomically update Java variable to x if it is currently
* holding expected.
* @return true if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
此方法不是Java實(shí)現(xiàn)的,而是通過JNI調(diào)用操作系統(tǒng)的原生程序,涉及到CPU原子操作,現(xiàn)在幾乎所有的CPU指令都支持CAS的原子操作,X86下對(duì)應(yīng)的是CMPXCHG匯編指令。
出于好奇,查看了下CAS原子操作的代碼描述:
int compare_and_swap(int* reg, int oldval, int newval) {
ATOMIC();
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
END_ATOMIC();
return old_reg_val;
}
也就是檢查內(nèi)存*reg里的值是不是oldval,如果是的話,則對(duì)其賦值newval。上面的代碼總是返回old_reg_value,調(diào)用者如果需要知道是否更新成功還需要做進(jìn)一步判斷,為了方便,它可以變種為直接返回是否更新成功,如下:
bool compare_and_swap (int *accum, int *dest, int newval)
{
if ( *accum == *dest ) {
*dest = newval;
return true;
}
return false;
}
兩種鎖各有優(yōu)缺點(diǎn),不可認(rèn)為一種好于另一種,像樂觀鎖適用于寫比較少的情況下,即沖突真的很少發(fā)生的時(shí)候,這樣可以省去了鎖的開銷,加大了系統(tǒng)的整個(gè)吞吐量。但如果經(jīng)常產(chǎn)生沖突,上層應(yīng)用會(huì)不斷的進(jìn)行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com