天天看點

【Concurrency】之 超越内置鎖(使用ReentrantLock)

前言

内置鎖雖然友善但限制很多:

  1. 一個線程因為等待内置鎖而進入阻塞之後,就無法中斷該線程了
  2. 嘗試擷取内置鎖時,無法設定逾時
  3. 獲得内置鎖,必須使用synchronized塊
    sychronized(object) {
       //  使用共享資源 ...
    }
               

一、使用ReetrantLock

ReetrantLock 提供顯式的

lock

unlock

方法

// ReetrantLock 代替 synchronized
Lock lock = new ReetrantLock();
lock.lock();
try {
    // 使用共享資源
} finally {
    lock.unlock();
}
           

(1)可中斷的鎖

【Concurrency】之 超越内置鎖(使用ReentrantLock)

(2)逾時

ReentrantLock 可以為擷取鎖的操作設定逾時時間

lock.tryLock(1000, TimeUnit.MILLISECONDES);

(3)交替鎖(head-over-hand locking)

在連結清單中插入一個節點。

1. 用鎖保護整個連結清單(但連結清單加鎖時其他使用者無法通路連結清單)

2. 交替鎖可以隻鎖住連結清單的一部分(允許不涉及被鎖部分的其他線程自由通路連結清單)

【Concurrency】之 超越内置鎖(使用ReentrantLock)

(4)條件變量

并發程式設計經常需要等待某個事件發生。
  1. 從隊列删除元素前需要等待隊列非空
  2. 向緩存添加資料前需要等待緩存有足夠的空間
// 建議按照下面的模式使用條件變量:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    while (! <<條件為真>>) {
        condition.await();
    }
    // 使用共享資源
} finally {
    lock.unlock();
}
           
當另一個線程調用了signal() 或者 signalAlll(),意味着對應的條件可能變為真,await()将原子地回複運作并重新加鎖。

二、原子變量

關于之前自增,java.util.concurrent.atomic包提供了更好的方案:

【Concurrency】之 超越内置鎖(使用ReentrantLock)

AtomicInteger 的 incrementAndGet()方法功能上等價于++count(AtomicInteger 也提供了getAndIncrement方法,等價于count++)。不過與++count 不同,incrementAndGet()方法是原子操作。

原子變量是無鎖(lock-free)非阻塞(non-blocking)算法的基礎,這種算法可以不用鎖和阻塞來達到同步的目的。

三、再想想

  1. ReentrantLock建立時可以設定一個描述公平性的變量。什麼是“公平”的鎖?何時适合使用公平的鎖?使用非公平的鎖會怎樣?
  2. 什麼是ReentrantReadWriteLock?它與ReentrantLock有什麼差別?使用與什麼場景?
  3. 什麼是“虛假喚醒”(spurious wakeup)什麼時候發生虛假喚醒?為什麼符合規範的代碼不用擔心虛假喚醒?
  4. 什麼是AtomicIntegerFieldUpdater?它與AtomicInteger有什麼差別?适合用于什麼場景?

四、小結

ReentrantLock和java.util.concurrent.atomic可以做:
  1. 線上程擷取鎖時中斷它
  2. 設定線程擷取鎖的逾時時間
  3. 按任意順序擷取和釋放鎖
  4. 用條件變量等待某個條件變成真
  5. 使用原子變量避免鎖的使用

繼續閱讀