前言
内置鎖雖然友善但限制很多:
- 一個線程因為等待内置鎖而進入阻塞之後,就無法中斷該線程了
- 嘗試擷取内置鎖時,無法設定逾時
- 獲得内置鎖,必須使用synchronized塊
sychronized(object) { // 使用共享資源 ... }
一、使用ReetrantLock
ReetrantLock 提供顯式的
lock
和
unlock
方法
// ReetrantLock 代替 synchronized
Lock lock = new ReetrantLock();
lock.lock();
try {
// 使用共享資源
} finally {
lock.unlock();
}
(1)可中斷的鎖
(2)逾時
ReentrantLock 可以為擷取鎖的操作設定逾時時間
lock.tryLock(1000, TimeUnit.MILLISECONDES);
(3)交替鎖(head-over-hand locking)
在連結清單中插入一個節點。
1. 用鎖保護整個連結清單(但連結清單加鎖時其他使用者無法通路連結清單)
2. 交替鎖可以隻鎖住連結清單的一部分(允許不涉及被鎖部分的其他線程自由通路連結清單)
(4)條件變量
并發程式設計經常需要等待某個事件發生。
- 從隊列删除元素前需要等待隊列非空
- 向緩存添加資料前需要等待緩存有足夠的空間
// 建議按照下面的模式使用條件變量:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while (! <<條件為真>>) {
condition.await();
}
// 使用共享資源
} finally {
lock.unlock();
}
當另一個線程調用了signal() 或者 signalAlll(),意味着對應的條件可能變為真,await()将原子地回複運作并重新加鎖。
二、原子變量
關于之前自增,java.util.concurrent.atomic包提供了更好的方案:
AtomicInteger 的 incrementAndGet()方法功能上等價于++count(AtomicInteger 也提供了getAndIncrement方法,等價于count++)。不過與++count 不同,incrementAndGet()方法是原子操作。
原子變量是無鎖(lock-free)非阻塞(non-blocking)算法的基礎,這種算法可以不用鎖和阻塞來達到同步的目的。
三、再想想
- ReentrantLock建立時可以設定一個描述公平性的變量。什麼是“公平”的鎖?何時适合使用公平的鎖?使用非公平的鎖會怎樣?
- 什麼是ReentrantReadWriteLock?它與ReentrantLock有什麼差別?使用與什麼場景?
- 什麼是“虛假喚醒”(spurious wakeup)什麼時候發生虛假喚醒?為什麼符合規範的代碼不用擔心虛假喚醒?
- 什麼是AtomicIntegerFieldUpdater?它與AtomicInteger有什麼差別?适合用于什麼場景?
四、小結
ReentrantLock和java.util.concurrent.atomic可以做:
- 線上程擷取鎖時中斷它
- 設定線程擷取鎖的逾時時間
- 按任意順序擷取和釋放鎖
- 用條件變量等待某個條件變成真
- 使用原子變量避免鎖的使用