淺談一下Java中的悲觀鎖和樂觀鎖
悲觀鎖和樂觀鎖是面試高頻問題之一,本文將對悲觀鎖和樂觀鎖簡單的進行一個介紹。
悲觀鎖(Pessimistic Locking)
悲觀鎖在并發(fā)環(huán)境中認為數(shù)據(jù)隨時會被其他線程修改,因此每次在訪問數(shù)據(jù)時都會加鎖,直到操作完成后才釋放鎖。悲觀鎖適用于寫操作多、競爭激烈的場景,比如多個線程同時對同一數(shù)據(jù)進行修改或刪除操作的情況。悲觀鎖可以保證數(shù)據(jù)的一致性,避免臟讀、幻讀等問題的發(fā)生
悲觀鎖就像一個大保安,總是認為有壞人想要偷走共享資源,于是它把資源護得緊緊的,不讓任何人接近,同時還會排隊等待資源,想要使用就得先獲取鎖,這樣雖然安全可靠,但是也會導致效率低下,因為別的線程必須等待鎖的釋放才能繼續(xù)執(zhí)行。
Java中常用的悲觀鎖是synchronized關鍵字和ReentrantLock類。
使用synchronized關鍵字實現(xiàn)悲觀鎖的代碼如下:
synchronized (lock) { //訪問共享資源的代碼塊 }
使用ReentrantLock實現(xiàn)悲觀鎖的代碼如下:
ReentrantLock lock = new ReentrantLock(); lock.lock(); try { //訪問共享資源的代碼塊 } finally { lock.unlock(); }
悲觀鎖存的問題:
- 效率低:悲觀鎖需要獲取鎖才能進行操作,當有多個線程需要訪問同一份數(shù)據(jù)時,每個線程都需要先獲取鎖,然后再進行操作,如果鎖競爭激烈,就會導致線程等待鎖的釋放,浪費了大量的時間。
- 容易引起死鎖:悲觀鎖在獲取鎖的過程中,如果獲取不到就會一直等待,如果不同的線程都在等待對方釋放鎖,就會導致死鎖的情況出現(xiàn)。
- 可能會引起線程阻塞:當某個線程獲取到鎖時,其他線程需要等待,如果等待的時間過長,就會導致線程阻塞,影響應用的性能。
樂觀鎖
樂觀鎖在并發(fā)環(huán)境中認為數(shù)據(jù)一般情況下不會被其他線程修改,因此在訪問數(shù)據(jù)時不加鎖,而是在更新數(shù)據(jù)時進行檢查。如果檢查到數(shù)據(jù)被其他線程修改,則放棄當前操作,重新嘗試更新。
樂觀鎖適用于讀操作多、寫操作少的場景,比如多個線程同時對同一數(shù)據(jù)進行讀取操作的情況。樂觀鎖可以減少鎖的競爭,提高系統(tǒng)的并發(fā)性能。
樂觀鎖就像一個樂天派,總是認為沒有壞人想要偷走共享資源,于是它就不怎么防范,直接對資源進行操作,如果沒有其他線程對資源進行修改,操作就會成功,否則就會進行重試,這樣雖然效率高,但是如果多個線程同時進行修改,就會導致競爭和沖突,需要進行額外的處理。
Java中常用的樂觀鎖是基于CAS(Compare and Swap,比較和交換)算法實現(xiàn)的。
CAS操作包括三個操作數(shù):內存地址V、舊的預期值A和新的值B。CAS操作首先讀取內存地址V中的值,如果該值等于舊的預期值A,那么將內存地址V中的值更新為新的值B;
否則,不進行任何操作。在更新過程中,如果有其他線程同時對該共享資源進行了修改,那么CAS操作會失敗,此時需要重試更新操作。
下面是一段基于CAS算法實現(xiàn)的樂觀鎖代碼:
// 假設共享資源為變量value,初始值為1 AtomicInteger value = new AtomicInteger(1); // 假設舊的預期值為1,新的值為2 int expect = 1; int update = 2; // 使用CAS操作更新共享資源的值 while (true) { // 讀取共享資源的當前值 int current = value.get(); // 如果當前值等于舊的預期值,使用CAS操作將新的值更新到共享資源中 if (current == expect) { if (value.compareAndSet(expect, update)) { // 更新成功,退出循環(huán) break; } else { // 更新失敗,可能是因為其他線程修改了共享資源的值,重試更新操作 continue; } } else { // 當前值不等于舊的預期值,說明共享資源的值已經(jīng)被其他線程修改,重試更新操作 continue; } }
在這段代碼中,內存地址V對應的是AtomicInteger對象value,舊的預期值A對應的是變量expect,新的值B對應的是變量update。使用AtomicInteger對象可以保證CAS操作的原子性,即只有一個線程能夠成功更新共享資源的值。使用compareAndSet
方法可以判斷共享資源的值是否等于舊的預期值,并嘗試將新的值更新到共享資源中。如果更新成功,就退出循環(huán);否則,說明共享資源的值已經(jīng)被其他線程修改,需要重試更新操作。
在實際應用中,樂觀鎖的實現(xiàn)通常比這個簡單實現(xiàn)要復雜。例如,在對數(shù)據(jù)庫中的數(shù)據(jù)進行更新時,需要在更新操作中同時更新版本號和其他字段的值,并且需要處理更新失敗和重試的情況。
樂觀鎖存在的問題
CAS雖然很?效的解決原?操作,但是CAS仍然存在三?問題:ABA問題,自旋時間過長和只能保證單個變量的原子性。
- ABA問題:CAS算法在比較和替換時只考慮了值是否相等,而沒有考慮到值的版本信息。如果一個值在操作過程中被修改了兩次,從原值變成新值再變回原值,此時CAS會認為值沒有發(fā)生變化,從而出現(xiàn)操作的錯誤。為了解決ABA問題,可以在共享資源中增加版本號,每次修改操作都將版本號加1,從而保證每次更新操作的唯一性。在更新數(shù)據(jù)時先讀取當前版本號,如果與自己持有的版本號相同,則可以更新數(shù)據(jù),否則更新失敗。版本號算法可以避免ABA問題,但需要維護版本號,增加了代碼復雜度和內存開銷。
- 自旋時間過長:由于CAS算法在失敗時會一直自旋,等待共享變量可用,如果共享變量一直不可用,就會出現(xiàn)自旋時間過長的問題,浪費CPU資源。
- 只能保證單個變量的原子性:CAS算法只能保證單個變量的原子性,如果需要多個變量的原子操作,就需要使用鎖等其他方式進行保護。
悲觀鎖和樂觀鎖的對比
悲觀鎖 | 樂觀鎖 | |
性能 | 低 | 高 |
數(shù)據(jù)一致性 | 高 | 低 |
實現(xiàn)復雜度 | 簡單 | 復雜 |
加鎖方式 | 基于鎖機制 | 基于版本號機制 |
應用場景 | 讀少寫多 | 讀多寫少 |
存在的問題 | 效率低、容易引起死鎖、可能會引起線程阻塞 | ABA問題、自旋時間過長、只能保證單個變量的原子性 |
總結
悲觀鎖和樂觀鎖各有優(yōu)缺點,應根據(jù)具體的業(yè)務場景和性能需求來選擇合適的鎖機制。在實際應用中,也可以考慮使用兩種鎖機制的組合,例如在高并發(fā)讀寫的情況下,可以使用樂觀鎖來提高讀操作的效率,同時在寫操作時使用悲觀鎖來保證數(shù)據(jù)的一致性。
到此這篇關于淺談一下Java中的悲觀鎖和樂觀鎖的文章就介紹到這了,更多相關Java悲觀鎖和樂觀鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java使用elasticsearch基礎API使用案例講解
這篇文章主要介紹了Java使用elasticsearch基礎API使用案例講解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-08-08實例講解Java中random.nextInt()與Math.random()的基礎用法
今天小編就為大家分享一篇關于實例講解Java中random.nextInt()與Math.random()的基礎用法,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02