天天看點

java mysql 事務鎖_資料庫事務&&Java鎖相關整理

資料庫事務:說一下事務的一些東西?你對事務的了解有哪些?說說資料庫的樂觀鎖和悲觀鎖?

InnoDB支援事務,MyISAM不支援事務

四種屬性

原子性、一緻性、隔離性、持久性

原子性:一個事務要麼成功送出,要麼不成功送出。基于undo log和復原操作實作。

一緻性:

隔離性:一個事務的操作過程中不能被其他事務感覺

持久性:事務的修改具有持久性

四種屬性的實作:

原子性:基于undo log和復原操作實作

一緻性:其他三種屬性保證一緻性

持久性:redo log,每執行一條事務中的sql就記錄在redo log上,事務一旦被送出redo log就将操作持久化到磁盤上。

隔離性:

名詞解釋:

讀未送出:允許讀取未送出的資料

讀已送出:隻允許讀取已送出的資料

可重複讀:一個事務兩次讀取同一資料,讀到的結果是一樣的

串行化:強制事務串行執行

髒讀:事務A修改的資料,但未送出;這時事務B能夠讀取到A未送出的修改。

不可重複讀:事務A兩次讀取的結果count沒變,但是内容被不同。

幻讀:事務A兩次讀取的結果count改變。

隔離性的實作方式一是加鎖,

一是MVCC:https://juejin.im/post/5c519bb8f265da617831cfff

Java中有哪些鎖:

為了友善記憶,将Java中線程與資源互相間的關系進行分類,分為三種類型:線程自身、線程與其他線程、線程與資源。具體如下圖所示:

java mysql 事務鎖_資料庫事務&&Java鎖相關整理

關于鎖的詳細講解,可參考下面你這篇文章:https://tech.meituan.com/2018/11/15/java-lock.html

1. 悲觀鎖和樂觀鎖

悲觀鎖:基于認為自己在使用臨界資料的時候,一定會有别的線程來競争,是以在悲觀鎖模式下,線程在擷取資料前先加鎖,確定資料不會被競争線程修改。Java中,sychronized關鍵字和Lock的實作類都是悲觀鎖。

樂觀鎖:基于認為自己在使用臨界資料的時候,不會有别的線程來競争,是以在樂觀鎖模式下,線程通路資料時不會加鎖。隻是在更新資料的時候,去判斷之前有沒有别的線程更新了這個資料,如果沒被更新則正常執行;如果被更新了,則根據不同的方式執行不同的操作,如報錯或自動重試。樂觀鎖在java中基于無鎖程式設計實作,通常使用的是CAS算法。

java mysql 事務鎖_資料庫事務&&Java鎖相關整理

悲觀鎖适合寫操作多的場景,先加鎖可以保證寫操作時資料正确。樂觀鎖适合讀操作多的場景,不加鎖的特點能夠使其讀操作的性能大幅提升。2.共享鎖/排他鎖

排他鎖是指該鎖一次隻能被一個線程所持有。如果線程T對資料A加上排它鎖後,則其他線程不能再對A加任何類型的鎖。獲得排它鎖的線程即能讀資料又能修改資料。JDK中的synchronized和JUC中Lock的實作類就是互斥鎖。

共享鎖是指該鎖可被多個線程所持有。如果線程T對資料A加上共享鎖後,則其他線程隻能對A再加共享鎖,不能加排它鎖。獲得共享鎖的線程隻能讀資料,不能修改資料。

3. 自旋鎖和自适應鎖

線程阻塞喚醒使用的資源有時會大于線程占用CPU繼續執行使用的資源,在這種情況下,一般會采用讓線程自旋的方式,來等待鎖釋放,這就是自旋鎖。

如果鎖被占用的時間很短,自旋等待的效果就會非常好。反之,如果鎖被占用的時間很長,那麼自旋的線程隻會白浪費處理器資源。為了更好的控制鎖的自旋次數,引用了自适應鎖。

自适應意味着自旋的次數由上一個線程在同一個鎖上的自旋時間,以及鎖的擁有者的狀态來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運作中,那麼虛拟機就會認為這次自旋很有可能再次成功,進而它将允許自旋等待持續相對更長的時間。如果對于某個鎖,自旋很少成功獲得過,那在以後嘗試擷取這個鎖時将可能省略掉自旋過程,直接阻塞線程,避免浪費處理器資源。

4. 可重入鎖/非可重入鎖

可重入鎖又名遞歸鎖,是指在同一個線程在外層方法擷取鎖的時候,再進入該線程的内層方法會自動擷取鎖(前提鎖對象得是同一個對象或者class),不會因為之前已經擷取過還沒釋放而阻塞。Java中ReentrantLock和synchronized都是可重入鎖,可重入鎖的一個優點是可一定程度避免死鎖。

通過重入鎖ReentrantLock以及非可重入鎖NonReentrantLock的源碼來對比分析一下為什麼非可重入鎖在重複調用同步資源時會出現死鎖。

首先ReentrantLock和NonReentrantLock都繼承父類AQS,其父類AQS中維護了一個同步狀态status來計數重入次數,status初始值為0。

當線程嘗試擷取鎖時,可重入鎖先嘗試擷取并更新status值,如果status == 0表示沒有其他線程在執行同步代碼,則把status置為1,目前線程開始執行。如果status != 0,則判斷目前線程是否是擷取到這個鎖的線程,如果是的話執行status+1,且目前線程可以再次擷取鎖。而非可重入鎖是直接去擷取并嘗試更新目前status的值,如果status != 0的話會導緻其擷取鎖失敗,目前線程阻塞。

釋放鎖時,可重入鎖同樣先擷取目前status的值,在目前線程是持有鎖的線程的前提下。如果status-1 == 0,則表示目前線程所有重複擷取鎖的操作都已經執行完畢,然後該線程才會真正釋放鎖。而非可重入鎖則是在确定目前線程是持有鎖的線程之後,直接将status置為0,将鎖釋放。

java mysql 事務鎖_資料庫事務&&Java鎖相關整理

5. 無鎖/偏向鎖/輕量級鎖/重量級鎖

四種鎖是指鎖的狀态,專門針對synchronized的。

無鎖:沒有對資源進行鎖定,可以由多個線程修改資源,但同時隻有一個能修改成功。無鎖的特點就是修改操作在循環内進行,線程會不斷的嘗試修改共享資源,CAS就是無鎖的展現。

偏向鎖:當一段代碼總是被某個線程通路時(可以簡單了解為系統中隻有這一個線程),該線程将會自動擷取鎖,降低擷取鎖的代價。當一個線程通路同步代碼塊并擷取鎖時,會在Mark Word裡存儲鎖偏向的線程ID,線上程進入和退出同步塊時不再通過CAS操作來加鎖和解鎖,而是檢測Mark Word裡是否存儲着指向目前線程的偏向鎖。線程不會主動釋放偏向鎖,偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有位元組碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處于被鎖定狀态,撤銷偏向鎖後恢複到無鎖(标志位為“01”)或輕量級鎖(标志位為“00”)的狀态。

輕量級鎖:當鎖是偏向鎖的時候,另外的線程通路該鎖,偏向鎖就會更新為輕量級鎖;另外的線程會通過自旋的方式等待,不會阻塞,提高性能。

重量級鎖:當等待線程的自旋超過一定次數,或一個持有鎖,一個自選等待,又有第三個線程來訪時輕量級鎖會更新為重量級鎖,此時等待鎖的線程都會進入阻塞狀态。

綜上:偏向鎖通過對比Mark Word解決加鎖問題,避免執行CAS操作。而輕量級鎖是通過用CAS操作和自旋來解決加鎖問題,避免線程阻塞和喚醒而影響性能。重量級鎖是将除了擁有鎖的線程以外的線程都阻塞。

6. 公平鎖/非公平鎖

公平鎖是多個線程按照申請鎖的先後順序擷取鎖。公平鎖的優點是等待鎖的線程不會餓死。缺點是整體吞吐效率相對非公平鎖要低,等待隊列中除第一個線程以外的所有線程都會阻塞,CPU喚醒阻塞線程的開銷比非公平鎖大。

非公平鎖是線程執行到臨界代碼塊時直接擷取鎖,擷取不到才排在等待隊列的隊尾;如果擷取到則直接執行。非公平鎖的優點是可以減少喚起線程的開銷,整體的吞吐效率高,因為線程有幾率不阻塞直接獲得鎖,CPU不必喚醒所有線程。缺點是處于等待隊列中的線程可能會餓死,或者等很久才會獲得鎖。

ReentrantLock裡面有一個内部類Sync,Sync繼承AQS(AbstractQueuedSynchronizer),添加鎖和釋放鎖的大部分操作實際上都是在Sync中實作的。它有公平鎖FairSync和非公平鎖NonfairSync兩個子類。ReentrantLock預設使用非公平鎖,也可以通過構造器來顯示的指定使用公平鎖。下面我們來看一下公平鎖與非公平鎖的加鎖方法的源碼:

java mysql 事務鎖_資料庫事務&&Java鎖相關整理

通過上圖中的源代碼對比,我們可以明顯的看出公平鎖與非公平鎖的lock()方法唯一的差別就在于公平鎖在擷取同步狀态時多了一個限制條件:hasQueuedPredecessors()。

java mysql 事務鎖_資料庫事務&&Java鎖相關整理

在hasQueuedPredecessors()方法中判斷目前線程是否位于同步隊列的第一個,如果是傳回true,否則傳回false。

更加詳細的資訊,請閱讀:https://tech.meituan.com/2018/11/15/java-lock.html