ReadWriteLock讀寫鎖升級的踩坑:Kotlin作弊,最好使用StampedLock - javaspecialists
在Java 5中,我們獲得了ReadWriteLock介面,並帶有ReentrantReadWriteLock實現。它具有明智的限制,我們可以將寫鎖降級為讀鎖,但不能將讀鎖升級為寫鎖。當我們嘗試時,我們將立即陷入死鎖。出現此限制的原因是,如果兩個執行緒都具有讀鎖,那麼如果兩個執行緒都嘗試同時升級怎麼辦?為了安全起見,它會始終使嘗試升級的所有執行緒陷入死鎖。、
降級ReentrantReadWriteLock可以正常工作,在這種情況下,我們可以同時持有讀取和寫入鎖定。降級意味著在持有寫鎖的同時,我們也鎖定了讀鎖,然後釋放了寫鎖。這意味著我們不允許任何其他執行緒寫入,但它們可以讀取。
import java.util.concurrent.locks.*; // This runs through fine public class DowngradeDemo { public static void main(String... args) { var rwlock = new ReentrantReadWriteLock(); System.out.println(rwlock); // w=0, r=0 rwlock.writeLock().lock(); System.out.println(rwlock); // w=1, r=0 rwlock.readLock().lock(); System.out.println(rwlock); // w=1, r=1 rwlock.writeLock().unlock(); // at this point other threads can also acquire read locks System.out.println(rwlock); // w=0, r=1 rwlock.readLock().unlock(); System.out.println(rwlock); // w=0, r=0 } } |
嘗試將ReentrantReadWriteLock從讀取升級為寫入會導致死鎖:
// This deadlocks public class UpgradeDemo { public static void main(String... args) { var rwlock = new ReentrantReadWriteLock(); System.out.println(rwlock); // w=0, r=0 rwlock.readLock().lock(); System.out.println(rwlock); // w=0, r=1 rwlock.writeLock().lock(); // deadlock System.out.println(rwlock); rwlock.readLock().unlock(); System.out.println(rwlock); rwlock.writeLock().unlock(); System.out.println(rwlock); } } |
Kotlin中的ReadWriteLock
讓我們看一下Kotlin如何管理ReadWriteLock。
下面是降級程式碼:
// DowngradeDemoKotlin.kt import java.util.concurrent.locks.* import kotlin.concurrent.* fun main() { val rwlock = ReentrantReadWriteLock() println(rwlock) // w=0, r=0 rwlock.write { println(rwlock) // w=1, r=0 rwlock.read { println(rwlock) // w=1, r=1 } println(rwlock) // w=1, r=0 } println(rwlock) // w=0, r=0 } |
下面是升級:
// UpgradeDemoKotlin.kt fun main() { val rwlock = ReentrantReadWriteLock() println(rwlock) // w=0, r=0 rwlock.read { println(rwlock) // w=0, r=1 rwlock.write { println(rwlock) // w=1, r=0 } println(rwlock) // w=0, r=1 } println(rwlock) // w=0, r=0 } |
竟然沒有發生死鎖。
如果我們窺視Kotlin擴充套件功能的實現,ReentrantReadWriteLock.write()將會看到以下內容:
Kotlin的擴充套件功能ReentrantReadWriteLock.write()透過在升級之前放開讀鎖來作弊,從而為競賽條件開啟了大門。
/ **
*在此鎖的寫鎖下執行給定的[action]。
*
*如果需要,該功能會從讀取鎖定升級為寫入鎖定,
*但是此升級不是原子升級,
因為[ReentrantReadWriteLock] 不支援此類升級。
*為了進行這種升級,此功能首先釋放
該執行緒持有的所有*讀鎖,然後獲取寫鎖,並且
*釋放後再重新獲取讀鎖。
*
*因此,如果已
透過檢查某些條件啟動了* 寫鎖
內部的[action] ,則必須在[action]內部重新檢查條件*以避免可能的爭用。
*
* @return操作的返回值。
* /
@kotlin.internal.InlineOnly public inline fun <T> ReentrantReadWriteLock.write(action: () -> T): T { val rl = readLock() val readCount = if (writeHoldCount == 0) readHoldCount else 0 repeat(readCount) { rl.unlock() } val wl = writeLock() wl.lock() try { return action() } finally { repeat(readCount) { rl.lock() } wl.unlock() } } |
原來,Kotlin的擴充套件功能ReentrantReadWriteLock.write()透過在升級之前放開讀鎖來作弊,從而為競爭開啟了漏洞大門。
使用StampedLock升級
Java 8 StampedLock使我們可以更好地控制應該如何處理失敗的升級。StampedLock 不是可重入的,這意味著我們不能同時持有讀取和寫入鎖。戳記未繫結到特定執行緒,因此我們也不能同時從一個執行緒持有兩個寫鎖。我們可以同時持有許多讀鎖,每個讀鎖都有不同的標記。但是我們只能得到一個寫鎖。這是一個演示:
public class StampedLockDemo { public static void main(String... args) { var sl = new StampedLock(); var stamps = new ArrayList<Long>(); System.out.println(sl); // Unlocked for (int i = 0; i < 42; i++) { stamps.add(sl.readLock()); } System.out.println(sl); // Read-Locks:42 stamps.forEach(sl::unlockRead); System.out.println(sl); // Unlocked var stamp1 = sl.writeLock(); System.out.println(sl); // Write-Locked var stamp2 = sl.writeLock(); // deadlocked System.out.println(sl); // Not seen... } } |
由於StampedLock不知道哪個執行緒擁有鎖,因此DowngradeDemo會死鎖:
public class StampedLockDowngradeFailureDemo { public static void main(String... args) { var sl = new StampedLock(); System.out.println(sl); // Unlocked long wstamp = sl.writeLock(); System.out.println(sl); // Write-Locked long rstamp = sl.readLock(); // deadlocked System.out.println(sl); // Not seen... } } |
但是,StampedLock確實允許我們嘗試升級或降級我們的鎖。這還將把戳記轉換為新型別。例如,這是我們如何正確進行降級。請注意,我們不需要解鎖寫鎖,因為戳記是從寫轉換為讀的。
public class StampedLockDowngradeDemo { public static void main(String... args) { var sl = new StampedLock(); System.out.println(sl); // Unlocked long wstamp = sl.writeLock(); System.out.println(sl); // Write-locked long rstamp = sl.tryConvertToReadLock(wstamp); if (rstamp != 0) { System.out.println("Converted write to read"); System.out.println(sl); // Read-locks:1 sl.unlockRead(rstamp); System.out.println(sl); // Unlocked } else { // this cannot happen (famous last words) sl.unlockWrite(wstamp); throw new AssertionError("Failed to downgrade lock"); } } } |
從讀鎖升級到寫鎖的程式碼:
public class StampedLockUpgradeDemo { public static void main(String... args) { var sl = new StampedLock(); System.out.println(sl); // Unlocked long rstamp = sl.readLock(); System.out.println(sl); // Read-locks:1 long wstamp = sl.tryConvertToWriteLock(rstamp); if (wstamp != 0) { // works if no one else has a read-lock System.out.println("Converted read to write"); System.out.println(sl); // Write-locked sl.unlockWrite(wstamp); } else { // we do not have an exclusive hold on read-lock System.out.println("Could not convert read to write"); sl.unlockRead(rstamp); } System.out.println(sl); // Unlocked } } |
與Kotlin ReentrantReadWriteLock.write()擴充套件功能不同,這將自動進行轉換。但是,它仍然可能失敗,例如,如果另一個執行緒當前也持有讀取鎖。在這種情況下,一種合理的方法是跳出並重試,或者以寫鎖定開始。
相關文章
- 原始碼分析:升級版的讀寫鎖 StampedLock原始碼
- Java讀寫鎖ReadWriteLockJava
- Istio 升級後踩的坑
- jQuery升級踩的那些坑jQuery
- StampedLock:JDK1.8中新增,比ReadWriteLock還快的鎖JDK
- Java併發(8)- 讀寫鎖中的效能之王:StampedLockJava
- 踩過的坑(一)——web容器升級Web
- 高併發之Phaser、ReadWriteLock、StampedLock
- Hexo6 升級踩坑指南Hexo
- java併發程式設計-StampedLock高效能讀寫鎖Java程式設計
- async語法升級踩坑小記
- Vue.js 升級踩坑小記Vue.js
- Java 讀寫鎖 ReadWriteLock 原理與應用場景詳解Java
- 多執行緒系列(十一) -淺析併發讀寫鎖StampedLock執行緒
- # Laravel 5.5 升級到 6.0 踩坑記錄Laravel
- babel 升級 7.X 踩坑記錄Babel
- jdk1.6升級jdk1.8踩出的神坑JDK
- Java開發者使用C++寫程式踩的坑JavaC++
- RxJava1 升級到 RxJava2 所踩過的坑RxJava
- MySQL5.5升級到MySQL5.7踩坑日記MySql
- Kotlin應用於專案踩過的坑Kotlin
- Java使用讀寫鎖替代同步鎖Java
- synchronized鎖的升級synchronized
- jenkins 升級踩坑,主要是403 CSRF報錯問題Jenkins
- MQTT使用踩坑MQQT
- 將自己的站點升級成HTTPS的(瘋狂踩坑)過程HTTP
- MySQL 避免行鎖升級為表鎖——使用高效的索引MySql索引
- oAuth2 升級Spring Cloud Finchley.RELEASE踩坑分享OAuthSpringCloud
- Vue1.0+Webpack1+Gulp專案升級構建方案的踩坑路VueWeb
- [踩坑] Go Modules 使用Go
- URLEncoder使用踩坑
- VUE 使用中踩過的坑Vue
- 鎖——Lock、Condition、ReadWriteLock、LockSupport
- 使用 Markdown 寫技術部落格,我踩過的 6個坑
- 【Vue】Vue1.0+Webpack1+Gulp專案升級構建方案的踩坑路VueWeb
- Flutter 入門與實戰(五十二):升級踩坑,聊聊 Dart 的 null safetyFlutterDartNull
- NPOI 2.0 – Excel讀寫神器再次升級薦Excel
- 又踩坑了!BigDecimal使用的5個坑!Decimal