5.Lock接口及其實現ReentrantLock


jdk1.7.0_79

  在java.util.concurrent.locks這個包中定義了和synchronized不一樣的鎖,重入鎖——ReentrantLock,讀寫鎖——ReadWriteLock等。在已經有了內置鎖synchronized的情況下,為什么又出現了Lock顯示鎖呢?本文將以Lock作為Java並發包源碼解讀的開始.

  Lock定義最基本的加鎖和解鎖操作。

  

Lock 

void lock(); 

阻塞方式獲取鎖,直到獲取鎖后才返回 

void locklnterruptibly(); 

獲取鎖,除非當前線程被中斷 

Condition newCondition(); 

返回一個Condition實例綁定到這個鎖實例 

boolean tryLock(); 

不管是否獲取到鎖,都立即返回,非阻塞 

boolean tryLock(long time, TimeUnit unit); 

在一定時間內阻塞獲取鎖 

void unlock(); 

釋放鎖 

  Lock接口有一個實現類——重入鎖ReentrantLock進入ReentrantLock類中我們就發現它對於Lock接口的實現基本上都借助於一個抽象靜態內部類Sync,該內部類繼承自AbstractQueuedSynchronizer,接着又發現兩個靜態內部類NonfairSyncFairSync,這兩個靜態內部類又是繼承自剛剛的Sync這里就要引入兩個新的概念了——公平鎖與非公平鎖。在公平的鎖上線程將按照它們發出請求的順序來獲得鎖但在非公平的鎖上則允許“插隊”:當一個線程請求非公平的鎖時,如果在發出請求的同時該鎖的狀態變為可用,那么這個線程將跳過隊列中所有的等待線程並獲得這個鎖。(《Java並發編程實戰》)

  ReentrantLock你可以稱之為重入鎖(遞歸鎖)、顯示鎖、排他鎖(獨占鎖),顯示鎖很好理解即線程在獲取鎖和釋放鎖的時候都需要代碼顯示操作。重入鎖是什么概念呢?synchronized實際上也是可重入鎖,意思就是一個線程已經持有這個鎖,當這個線程再次獲得這個鎖的時候不會被阻塞,而是使其同步狀態計數器1,這樣做的目的當然就是為了防止死鎖,當線程釋放這個鎖的時候,同步狀態計數器-1,直到遞減至0才表示這個鎖完全釋放完畢,其他線程可以獲取。那什么又是排他鎖呢?我們直到AQS定義了兩種模式下獲取鎖與釋放鎖的操作,那就是獨占模式和共享模式,所謂獨占模式就是只有一個線程能持有這個鎖,而共享模式則是這個鎖可以由多個線程所持有。例如ReebtrabtReadWriteLock的讀鎖就能由多個線程所持有。在知道了ReentrantLock的特性之后,我們再來看它其內部實現。 

  在前兩節解析AQS的時候我們就提到,AQS所提供的同步器是實現鎖的基礎框架,固然ReentrantLock同樣也是基於AQS,而ReentrantLock並沒有直接實現AQS抽象類,而是將在其內部定義一個Sync內部類來聚合AQS,這樣聚合而不是繼承的目的是為了將鎖的具體實現與鎖的使用做一個隔離,鎖的使用者關心的是鎖如何才能被正確使用,而鎖的實現者關心的是鎖如何基於AQS被正確的實現。先討論ReentrantLock$Sync抽象內部類在討論前先回顧一下能夠被子類重寫的AQS方法有哪些: 

  

AbstractQueuedSynchronizer 

protected boolean tryAcquire(int arg) 

子類可實現在獨占模式下獲取同步狀態的具體方法。 

protected boolean tryRelease(int arg) 

子類可實現在獨占模式下釋放同步狀態的具體方法。 

protected int tryAcquireShared(int arg) 

子類可實現在共享模式下獲取同步狀態的具體方法。 

protected int tryReleaseShared() 

子類可實現在共享模式下釋放同步狀態的具體方法。 

protected boolean isHeldExclusively() 

當前同步器是否在獨占模式下被線程占用,一般表示該方法是否被當前線程所獨占。 

  通過查看ReentrantLock$Sync的源碼可知,Sync一共對AQS重寫了這么幾個方法: 

  protected final boolean tryRelease(int release) 
  protected final boolean isHeldExclusively() 

  為什么Sync只重寫了這兩個方法呢?實際上在ReentrantLock的內部還有另外兩個內部類NonfairSync非公平鎖和FairSync公平鎖,這兩個內部類是Sync的具體實現,很顯然能夠得出,對於鎖的獲取非公平鎖和公平鎖的實現是不一樣的,而對於鎖的釋放兩者均是相同實現。針對ReentrantLock的非公平鎖和公平鎖接下來我們來一一探討他們的不同點和相同點。 

  ReentrantLock定義了一個成員變量——sync,並且他提供了兩個構造方法,其默認無參構造方法創建的是非公平鎖,而有參的構造方法則傳入一個boolean類型來決定構造一個公平鎖還是非公平鎖。 

public class ReentrantLock implements Lock { 
    private final Sync sync; 
    public ReentrantLock() { 
        sync = new NofairSync(); 
    } 
    public ReentrantLock(boolean fair) { 
        sync = fair ? new FairSync() : new NonfairSync(); 
    } 
    …… 
} 

  1.lock()

  針對開篇提到的Lock接口定義的方法,我們先來看ReentrantLockLock#lock的實現: 

public class ReentrantLock implements Lock { 
  …… 
  public void lock() { 
    sync.lock(); 
  }   
  …… 
}

  這個方法是抽象內部類定義的一個抽象方法,從命名可以看到這個類實際上就是AQSacquire獲取鎖的具體實現,在這里我們能看到非公平鎖和公平鎖對獲取鎖的不同實現,我們先來看非公平鎖對Sync#lock的實現: 

static final class NonfairSync extends Sync { 
  final void lock() { 
    if (compareAndSetState(0, 1))  
       setExclusiveOwnerThread(Thread.currentThread()); 
    else 
      acquire(1); 
  } 
  protected final boolean tryAcquire(int acquire) {//在ReentrantLock$NonFairLock才終於看到了對AbstractQueuedSynchronizer#tryAcquire的具體實現。 
    return nonfairTryAcquire(acquires);//而tryAcquire的實現實際上又是在其父類ReentrantLock$Lock中實現的,好像有點繞,一會子類實現,一會父類實現。可以先這么來理解,既然它把tryAcquire的具體實現又定義在了父類,那說明這一定是父類對公共方法的抽取(Extract Method),其他地方一定有用到nonfairTryAcquire方法,不然JDK的作者不會閑的蛋疼。 
  } 
} 

  ReentrantLock$Sync中非公平鎖的獲取鎖的實現 

final boolean nonfairTryAcquire(int acquires) { 
    final Thread current = Thread.currentThread(); 
    int c = getState(); 
    if (c == 0) { 
        if (compareAndSetState(0, acquires)) { 
            setExclusiveOwnerThread(current); 
            return true; 
        } 
    } 
    else if (current == getExclusiveOwnerThread()) { 
        int nextc = c + acquires; 
        if (nextc < 0) // overflow 
            throw new Error("Maximum lock count exceeded"); 
        setState(nextc); 
        return true; 
    } 
    return false; 
} 

  我們說回ReentrantLock$NonFairLock非公平鎖的lock阻塞獲取鎖的實現,在調用非公平鎖的lock方法時,就會首先進行“搶鎖”操作,也就是compareAndSetState這個方法是利用的CAS底層方法看能否搶占到鎖,而不是按照先后獲取獲取鎖的方式放到同步隊列中獲取鎖,公平鎖就是這樣。既然說到了公平鎖獲取鎖的方式,我們不妨和ReentrantLock$FairLock作一個對比: 

static final class FairSync extends Sync { 
  inal void lock() { 
    acquire(1); 

  } 
  protected final boolean tryAcquire(int acquires) {//在ReentrantLock$NonFairLock我們看到它老老實實的實現了AQS定義的tryAcquire方法,而沒有調用父類的方法,從這里我們也基本能推斷在ReentrantLock中沒有其他地方會引用到這個方法。 
    final Thread current = Thread.currentThread(); 
    int c = getState(); 
    if (c == 0) { 
      if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { 
        setExclusiveOwnerThread(current); 
        return true; 
      } 
    } 
    else if (current == getExclusiveOwnerThread()) { 
      int nextc = c + acquires; 
      if (nextc < 0)  
        throw new Error(“Maximum lock count exceeded”); 
      setState(nextc); 
      return true; 
    }     
    return false; 
  } 
} 

  從公平鎖(nonfairTryAcquire)公平鎖(tryAcquire)的兩個方法對比可知:公平鎖在獲取鎖的時候首先會判斷當前線程是否有前驅節點已經在隊列中等待,如果有返回true,則不進行獲取鎖的操作,這一點就是和公平鎖最大的區別,只有當前線程沒有前驅節點才獲取鎖。ReentrantLock默認構造非公平鎖,而實際上用得最多的也是非公平鎖,公平鎖從一定程度上能防止“飢餓”,但非公平鎖在性能上卻優於公平鎖,我們做以下試驗得知的確如此(詳細試驗過程《【試驗局】ReentrantLock中非公平鎖與公平鎖的性能測試》 

  2.lockInterruptibly()

這個方法和lock方法的區別就是,lock會一直阻塞下去直到獲取到鎖,而lockInterruptibly則不一樣,它可以響應中斷而停止阻塞返回。ReentrantLock對其的實現是調用的Sync的父類AbstractQueuedSynchronizer#acquireInterruptibly方法: 

//ReentrantLock#lockInterruptibly 
public void lockInterruptibly() throws InterruptedException { 
    sync.acquireInterruptibly(1);//因為ReentrantLock是排它鎖,故調用AQS的acquireInterruptibly方法 
} 
//AbstractQueuedSynchronizer#acquireInterruptibly 

public final void acquireInterruptibly(int arg) throws InterruptedException{ 
  if (Thread.interrupted()) //線程是否被中斷,中斷則拋出中斷異常,並停止阻塞 
    throw new InterruptedException; 
  if (!tryAcquire(arg)) //首先還是獲取鎖,具體參照上文 
    doAcquireInterruptibly(arg);//獨占模式下中斷獲取同步狀態 
} 

  通過查看doAcquireInterruptibly的方法實現不難發現它和acquireQueued大同小異,前者拋出異常,后者返回boolean。具體實在不再討論,參照源碼以及《2.從AbstractQueuedSynchronizer(AQS)說起(1)——獨占模式的鎖獲取與釋放》 

  3.tryLock() 

  此方法為非阻塞式的獲取鎖,不管有沒有獲取鎖都返回一個boolean值。 

//ReentrantLock#tryLock 
public boolean tryLock() { 
  return sync.nonfairTryAcquire(1); 
} 

  可以看到它實際調用了Sync#nonfairTryAcquire非公平鎖獲取鎖的方法,這個方法我們在上文lock()方法非公平鎖獲取鎖的時候有提到,而且還特地強調了該方法不是在NonfairSync實現,而是在Sync中實現很有可能這個方法是一個公共方法,果然在非阻塞獲取鎖的時候調用的是此方法。詳細解析參照上文。 

  4.tryLock(long timeout, TimeUnit unit) 

此方法是表示在超時時間內獲取到同步狀態則返回true,獲取不到則返回false。由此可以聯想到AQStryAcquireNanos(int arg, long nanosTimeOut)方法 

//ReentrantLock#tryLock 
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { 
  return sync.tryAcquireNanos(1, unit.toNanos(timeout)); 
} 

  果然Sync實際上調用了父類AQStryAcquireNanos方法。 

//AbstractQueuedSynchronizer#tryAcquireNanos 

public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { 
  if (Thread.interrupted())  
    throw new InterruptedException();//可以看到前面和lockInterruptibly一樣 
  return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);//首先也會先嘗試獲取鎖 
} 

  在doAcquireNanos實際也和acquireQueueddoAcquireInterruptibly差不多,不同的是增加了超時判斷。 

  關於LockReentrantLock介紹到這里,在AQS和這里遺留了一個問題——Condition,在下一節中單獨介紹Condition 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM