《 Java併發編程的藝術》之Java中的鎖(第五章)

1 Lock接口
Lock和synchronized有何區別,區別在於synchronized是鎖一個代碼塊或一個方法,需要先獲取鎖再釋放鎖,可操作性比不上Lock。而Lock的可操作性在於我們可以像下面這樣操作:
------------------------------------------------------
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
lock1.lock();
lock2.lock();
lock1.unlock();
lock2.unlock();
------------------------------------------------------
分析:以上代碼說明了Lock的可操作性,lock1拿到了鎖,然後lock2獲取到了鎖後釋放lock1鎖,可以將鎖一層嵌套一層,可操作性比synchronized強。
 
2 隊列同步器
隊列同步器AbstractQueuedSynchronizer,簡稱AQS
其作爲java.util.concurrent包下不少類如(ReentrantLock,CountDownLactch等)的鎖或同步組件的基礎。內部維護了一個state狀態+隊列用於處理併發的請求。
 
同步器提供了模板方法,其模板方法分爲如下三類:
  • 獨佔式獲取和釋放同步狀態(state) tryAcquire()、tryRelease()
  • 共享式獲取和釋放同步狀態(state) tryAcquireShared()tryReleaseShared()
  • 查詢同步隊列等待線程情況           isHeldExclusively()
可用於繼承實現自己的同步組件,其實現代碼如下:
public class Mutex implements Lock {
 
  // 靜態內部類,自定義同步器
 private static class Sync extends AbstractQueuedSynchronizer {
  // 是否處於佔用狀態
  @Override
  protected boolean isHeldExclusively() {
   return getState() == 1;
  }
 
  // 當狀態爲0的時候獲取鎖
  @Override
  protected boolean tryAcquire(int acquires) {
   if (compareAndSetState(0, 1)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
   }
   return false;
  }
 
  // 釋放鎖,將狀態設置爲0
  @Override
  protected boolean tryRelease(int releases) {
   if(getState() == 0) throw new IllegalMonitorStateException();
   setExclusiveOwnerThread(null);
   setState(0);
   return true;
  }
 
  // 返回一個Condition,每個condition都包含了一個condition隊列
  Condition newCondition() { return new ConditionObject(); }
 }
 // 僅需要將操作代理到Sync上即可
 private final Sync sync = new Sync();
 @Override
 public void lock() { sync.acquire(1); }
 @Override
 public boolean tryLock() { return sync.tryAcquire(1); }
 @Override
 public void unlock() { sync.release(1); }
 @Override
 public Condition newCondition() { return sync.newCondition(); }
 public boolean isLocked() { return sync.isHeldExclusively(); }
 public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
 @Override
 public void lockInterruptibly() throws InterruptedException {
  sync.acquireInterruptibly(1);
 }
 @Override
 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
  return sync.tryAcquireNanos(1, unit.toNanos(timeout));
 }
}
分析:首先使用一個類實現了Lock接口,然後在內部實現一個Sync(繼承AQS)同步組件。最後通過調用同步組件的方法來達到同步的目的。
 
3 可重入鎖
支持重新進入的鎖,該鎖支持一個線程對資源的重複加鎖。其中synchronized和ReentrantLock都支持可重入鎖。
另外,鎖有一個公平性,等待越久的線程約會先獲取鎖(順序FIFO獲取),避免"飢餓"現場(ReentrantLock可控制是否公平鎖)。
 
實現重進入
什麼是重進入?
任意線程獲取鎖之後,能夠再次獲取鎖而不會被阻塞
 
// 以下是詳細代碼===============================
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();// 1 如果非獲取到鎖的線程,獲取狀態
    if (c == 0) {  // 如果狀態爲空則釋放了所有,當前新的線程可索取鎖
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {  
        // 2 如果當前線程是那個拿到鎖的線程,就直接進入,並且給他的狀態值自增
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
// 上面因爲當前擁有鎖的再次進入會增加state值大小,所以需要在釋放的時候進行處理
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;  //1 state減去釋放的值
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { //如果當前state=0,那就是真的釋放操作,並且使用setExclusiveOwnerThread釋放
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
 
4 讀寫鎖(不講解,感興趣可自行學習)
Java中讀寫鎖一般指的是 ReentrantReadWriteLock,可將讀寫分離,可多次重複讀(共享),但是寫的時候是互斥(排他)的。
ReentrantReadWriteLock自定義了同步器,通過同步器狀態維護了多個讀線程 + 一個寫線程。
 
5 LockSupport工具
方法如下:
 

 
使用例子如下:
       //獲取當前線程
        final Thread currentThread = Thread.currentThread();
     
        //在park之前先進行一次unpark        
        LockSupport.unpark(currentThread);
        
        System.out.println("開始阻塞!");
        // 由於在park之前進行了一次unpark,所以會抵消本次的park操作。因而不會阻塞在此處        
        LockSupport.park(currentThread);
        System.out.println("結束阻塞!");
 
參考文章:
 
6 Condition接口
當前接口用於 通知/等待,但是操作前均需要獲取鎖,是基於lock接口來實例化的。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
    lock.lock();  //使用前先獲取鎖
    try {
        condition.await();  //釋放鎖並進入阻塞狀態
    } finally {
        lock.unlock();
    }
}
public void conditionSignal() throws InterruptedException {
    lock.lock();
    try {
        condition.signal();  //通知獲取鎖
    } finally {
        lock.unlock();
    }    
}
 
實現分析(以下是ConditionObject源碼,分析Condition時均以這個說明,其入口在ReentrantLock.newCondition()):
// 1 調用await時
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
     // await時,當前線程會先進入等待隊列
    Node node = addConditionWaiter();
     // 然後釋放同步狀態,也就是釋放對應的鎖
    int savedState = fullyRelease(node);
    int interruptMode = 0;
     // 然後喚醒同步隊列後繼節點
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
 
// 2 調用signal喚醒
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter // 因爲await時會加新的線程加入等待隊列尾節點,所以首節點是加入最久的,這時候喚醒的話會取首節點(等最久的)去喚醒
    if (first != null)
        doSignalAll(first);
}
// 最後`signal`還是調用了以下方法來進行喚醒,LockSupport.unpark(thread);
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
 
 
 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章