深刻理解鎖機制(Java)

Java提供了豐富種類的鎖,在適當的場景使用合適的鎖可以展示出很是高的效率。經常使用到的鎖就有樂觀鎖和悲觀鎖。算法

樂觀鎖和悲觀鎖是一種廣義上的定義,體現了看代線程同步的不一樣角度,在Java和數據庫中都有此類的應用。例如樂觀鎖在數據庫的應用有加個版本號。數據庫

先來從概念來分析兩類鎖:對於同一數據的併發操做,悲觀鎖認爲本身在使用數據的時候必定會有別的線程來修改數據,所以在獲取數據的時候會先加鎖,保證數據不會被別的線程修改,在Java中Synchronized關鍵字和Lock的實現類都是悲觀鎖。bash

樂觀鎖則認爲本身在使用數據的時候不會有其它線程來修改數據,因此不會在獲取數據的時候去加鎖,只是在更新數據的時候去判斷以前有沒有別的線程更新了數據。若是以前沒有別的線程更新數據,當前線程就會將本身修改的數據寫入;若是以前有其它的線程對數據進行了修改,則根據實際狀況進行操做(如報錯或自動重試),通常會使用版本號機制或CAS操做實現。併發

update table set x=x+1, version=version+1 where id=#{id} and version=#{version}; 複製代碼



總結一下就是:函數

  • 悲觀鎖適合寫操做多的場景,先加鎖能夠保證寫操做時數據正確
  • 樂觀鎖適合讀操做多的場景,不加鎖的特色可以使其讀操做的性能大幅提高

來看一下樂觀鎖和悲觀鎖的調用方式:性能

悲觀鎖調用方式:ui

public synchronized void method(){
    //todo 操做同步資源
}
private ReentrantLock lock = new ReentrantLock();
public void modifyPublicResource(){
    lock.lock();
    //todo 操做同步資源
    lock.unlock();
}複製代碼

樂觀鎖調用方式:atom

//保證多個線程使用同一個AtomicInteger
private AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.incrementAndGet(); //執行自增1複製代碼

咱們能夠發現悲觀鎖基本都是在顯式的鎖定以後再操做同步資源,而樂觀鎖則直接去操做同步資源。
spa

下面來看樂觀鎖CAS算法(比較與交換),這是一種無鎖算法。涉及到三個操做數:線程

V:須要讀寫內存值;A:進行比較的值;B:要寫入的新值

當且僅當V的值等於A時,CAS經過原子的方式用新值B來更新V的值,不然不會執行任何操做。咱們來看看上面AtomicInteger的源碼:


上面代碼已經標了V、A、B的註釋,其中value這個值須要用volatile來修飾,保證其可見性。

接下來看看AtomicInteger的自增函數incrementAndGet


發現調用了unsafe.getAndAddInt(),再來看看:


根據源碼咱們能夠看出,getAndAddInt()循環獲取給定對象var1中的偏移量處的值var5,而後判斷內存值是否等於var5,若是相等就將內存設置爲var5+var4,不然返回false,繼續循環進行重試,直到設置成功才能退出循環,而且將舊值var5返回,整個「比較+更新」操做屬於原子操做,能夠保證多個線程都可以看到同一個變量的修改值。

相關文章
相關標籤/搜索