Java 併發編程 —— 悲觀鎖與樂觀鎖
簡介
爲避免多線程環境下,併發事務造成ACID錯誤,更合理的使用Spring 事務隔離屬性,這篇文章主要介紹如何通過悲觀鎖與樂觀鎖來限制Spring事務隔離屬性使用不當的問題。
悲觀鎖
悲觀鎖(Pessimistic Lock) —— 比較悲觀的鎖。
認爲修改數據時存在着其它連接也想修改此數據的事務。
介紹
- 悲觀鎖每次獲取數據都要鎖數據
(共享資源每次只給一個線程使用)
- 另外的線程獲取此數據時是block,直到數據被解鎖纔可以使用
(其它線程阻塞,用完後再把資源轉讓給其它線程)
- 常見的悲觀鎖實現(數據庫層面,在以下操作前會鎖住數據)
- 行鎖
- 表鎖
- 讀鎖
- 寫鎖
- Java(程序層面,重入鎖與同步鎖都是悲觀鎖的一種實現)
- ReentrantLock(重入鎖)
- synchronized(同步鎖)
如何使用
數據庫行鎖
FOR UPDATE
# 可在MyBatis使用的時候在SQL中將數據庫記錄鎖住(行鎖)
SELECT CLOUMN_A, CLOUMN_B, CLOUMN_C FROM TABLE WHERE CLOUMN_A = 'PARAM_A' FOR UPDATE
# 上面的SQL鎖住了TABLE中條件爲(CLOUMN_A = 'PARAM_A')的記錄。此事務提交前(提交後會釋放行鎖),其它連接無法使用。
# FOR UPDATE:是行鎖的一個關鍵字,會鎖住這張表的這條記錄,事務提交之後,才允許其它連接使用。
Hibernate 悲觀鎖實現
String sql = "SELECT CLOUMN_A, CLOUMN_B, CLOUMN_C FROM TABLE WHERE CLOUMN_A = 'PARAM_A'";
Query query = session.createQuery(sql);
query.setLockMode("SQL_OBJ",LockModel.UPGRADE);
Hibernate 加鎖模式
- Hibernate內部使用使用鎖
- LockMode.NONE
無鎖機制 - LockMode.WRITE
Insert和Update記錄的時候會自動獲取 - LockMode.READ
在讀取記錄時會自動獲取
- LockMode.NONE
- 數據庫控制鎖
- LockMode.UPGRADE
利用數據庫的for update字句加鎖
- LockMode.UPGRADE
注意事項
使用悲觀鎖,必須關閉MySql數據庫自動提交屬性,MySql默認使用autocommit模式,當執行一個更新操作後,MySql會立刻將結果進行提交。 set autocommit = 0;
使用START TRANSACTION,自動提交將保持禁用狀態,直到使用COMMIT或ROLLBACK結束事務。自動提交模式然後恢復到之前的狀態(如果start transaction 前 autocommit = 1,則完成本次事務後autocommit 還是1 。 如果start transaction 前 autocommit = 0, 則完成本次事務後 autocommit 還是0)
樂觀鎖
樂觀鎖(Optimistic Lock) 每次取數據的時候都認爲別人不會修改,所以不鎖。
認爲在短暫的時間裏不會有事務來修改此數據庫的數據!
介紹
- 大多基於數據版本(Version)
- 數據版本:數據增加一個版本標識,基於數據庫表的版本解決方案中,一般是通過爲數據表增加一個“version”字段來實現
- CAS算法實現 (點擊打開 Java算法 —— CAS實現)
- Java 實現
- java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。
如何使用
版本標識
- 讀取數據時,讀取此數據的版本號
- 更新時,對此版本號加一
- 提交對版本數據與數據庫表對應記錄的當前版本信息進行對比
- 如果提交的版本號大於數據庫當前版本,則更新,否則認爲版本過期,事務回滾報錯
注意事項
適用於多讀應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。