ReentrantReadWiteLock詳解

???????對(duì)于一般的鎖骂维,如ReentrantLock,其一般都是“獨(dú)占式”的扼脐,也即在同一時(shí)刻只有一個(gè)線程能夠訪問鎖定的代碼肠仪。如果對(duì)于共享資源的訪問“讀”多于“寫”肖抱,那么獨(dú)占鎖將沒有“讀寫鎖”有效。所謂“讀寫鎖”指的是對(duì)共享資源的訪問提供一個(gè)讀鎖和一個(gè)寫鎖藤韵,當(dāng)訪問方式是讀取操作時(shí)虐沥,使用讀鎖即可,當(dāng)訪問方式是修改操作時(shí)泽艘,則使用寫鎖欲险。讀鎖和寫鎖相互之間存在一定的制約條件:

  • 當(dāng)有讀鎖鎖定資源時(shí),其余的讀鎖可以共享式的訪問資源匹涮,但是會(huì)阻塞寫鎖對(duì)資源的獲忍焓浴;
  • 當(dāng)有寫鎖鎖定資源時(shí)然低,將阻塞其余所有的讀鎖(除了該線程本身)和寫鎖的獲认裁俊;

???????在Java中雳攘,提供了一個(gè)通用的接口來表示讀寫鎖带兜,即ReadWriteLock,該類提供了兩個(gè)方法:readLock()和writeLock()用于對(duì)讀鎖和寫鎖的獲取吨灭,具體的聲明如下:

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

???????除此之外刚照,Java還提供了該接口的兩個(gè)實(shí)現(xiàn):ReadWriteLockView和ReentrantReadWriteLock。第一個(gè)類是StampedLock類的內(nèi)部類喧兄,第二個(gè)則是我們正澄夼希可以使用的類,這里我們主要對(duì)ReentrantReadWriteLock進(jìn)行講解吠冤。

???????ReentrantReadWriteLock是一個(gè)復(fù)合詞浑彰,其拆分開來為reentrant read write lock。reentrant是entrant加上re前綴拯辙,表示可重入的郭变,read write lock則表示其是一個(gè)讀寫鎖,整體理解也即是“可重入的讀寫鎖”涯保《希可以看出,ReentrantReadWriteLock不僅實(shí)現(xiàn)了ReadWriteLock接口的規(guī)范遭赂,并且還提供了可重入的特性。如下是使用ReentrantReadWriteLock實(shí)現(xiàn)對(duì)緩存數(shù)據(jù)的訪問的一個(gè)簡(jiǎn)單示例:

public class MapCache<T> {
  private ReadWriteLock lock = new ReentrantReadWriteLock();
  private Map<String, T> cache = new HashMap<>();

  public void put(String key, T value) {
    lock.writeLock().lock();
    try {
      cache.put(key, value);
    } finally {
      lock.writeLock().unlock();
    }
  }

  public T get(String key) {
    lock.readLock().lock();
    try {
      T result = cache.get(key);
      return result;
    } finally {
      lock.readLock().unlock();
    }
  }
}

???????可以看到横辆,在對(duì)緩存數(shù)據(jù)進(jìn)行讀取時(shí)使用讀鎖撇他,而進(jìn)行修改時(shí)則使用寫鎖茄猫。下面為了展示讀寫鎖的是如何工作的,我們編寫了如下示例:

public class ReadWriteLockTest {
  public static void main(String[] args) {
    RWLCache cache = new RWLCache();
    Random random = new Random();
    for (int i = 0; i < 10; i++) {
      boolean flag = random.nextBoolean();
      Thread thread = new Thread(generateTask(flag, cache), generateThreadName(flag, i));
      thread.start();
    }
  }

  private static Runnable generateTask(boolean flag, RWLCache cache) {
    Runnable putTask = () -> cache.write();
    Runnable getTask = () -> cache.read();
    return flag ? putTask : getTask;
  }

  private static String generateThreadName(boolean flag, int index) {
    return flag ? "write-" + index : "read-" + index;
  }
}

???????其中RWLCache的具體實(shí)現(xiàn)如下:

public class RWLCache {
  private MyReentrantReadWriteLock lock = new MyReentrantReadWriteLock();

  public void write() {
    lock.writeLock().lock();
    try {
      System.out.println(Thread.currentThread().getName() + " acquires write lock, queued threads is " + getThreadNames(lock.getQueuedThreads()));
      sleep(1);
    } finally {
      System.out.println(Thread.currentThread().getName() + " releases write lock");
      lock.writeLock().unlock();
    }
  }

  public void read() {
    lock.readLock().lock();
    try {
      sleep(1);
      System.out.println(Thread.currentThread().getName() + " acquires read lock, queued threads is " + getThreadNames(lock.getQueuedThreads()));
    } finally {
      System.out.println(Thread.currentThread().getName() + " releases read lock");
      lock.readLock().unlock();
    }
  }

  private static final class MyReentrantReadWriteLock extends ReentrantReadWriteLock {
    @Override
    protected Collection<Thread> getQueuedThreads() {
      List<Thread> threads = new ArrayList<>(super.getQueuedThreads());
      Collections.reverse(threads);
      return threads;
    }
  }

  private void sleep(int seconds) {
    try {
      TimeUnit.SECONDS.sleep(seconds);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  private String getThreadNames(Collection<Thread> threads) {
    StringBuilder result = new StringBuilder("[");
    Iterator<Thread> iterator = threads.iterator();
    if (!threads.isEmpty()) {
      Thread thread = iterator.next();
      result.append(thread.getName());
    }

    while (iterator.hasNext()) {
      Thread thread = iterator.next();
      result.append(",").append(thread.getName());
    }

    return result.append("]").toString();
  }
}

???????可以看到困肩,在每次讀寫鎖的獲取和釋放時(shí)划纽,我們都打印了等待隊(duì)列中的線程,如下是該程序的輸出結(jié)果:

read-0 acquires read lock, queued threads is [write-2,read-3,write-4,write-5,read-6,read-7,write-8,read-9]
read-1 acquires read lock, queued threads is [write-2,read-3,write-4,write-5,read-6,read-7,write-8,read-9]
read-1 releases read lock
read-0 releases read lock
write-2 acquires write lock, queued threads is [read-3,write-4,write-5,read-6,read-7,write-8,read-9]
write-2 releases write lock
read-3 acquires read lock, queued threads is [write-4,write-5,read-6,read-7,write-8,read-9]
read-3 releases read lock
write-4 acquires write lock, queued threads is [write-5,read-6,read-7,write-8,read-9]
write-4 releases write lock
write-5 acquires write lock, queued threads is [read-6,read-7,write-8,read-9]
write-5 releases write lock
read-7 acquires read lock, queued threads is [write-8,read-9]
read-7 releases read lock
read-6 acquires read lock, queued threads is [write-8,read-9]
read-6 releases read lock
write-8 acquires write lock, queued threads is [read-9]
write-8 releases write lock
read-9 acquires read lock, queued threads is []
read-9 releases read lock

???????從輸出結(jié)果看出锌畸,read-0和read-0兩個(gè)線程在還未釋放讀鎖的情況下同時(shí)獲取到了讀鎖勇劣,而后續(xù)的寫鎖則被阻塞在隊(duì)列中的;從寫線程對(duì)鎖的獲取和釋放可以看出潭枣,其在執(zhí)行過程中是會(huì)阻塞其余的讀鎖和寫鎖的比默;另外,仔細(xì)觀察read-6和read-7這兩個(gè)線程盆犁,其在等待隊(duì)列中的順序是read-6在前命咐,而read-7在后,但是在后續(xù)輸出的時(shí)候是read-7在前谐岁,而read-6在后醋奠,這是因?yàn)樵趓ead-6獲取到讀鎖之后,由于讀鎖是共享的伊佃,其會(huì)喚醒后續(xù)緊接著的嘗試獲取讀鎖的線程窜司,因而read-6和read-7幾乎是同時(shí)獲取到讀鎖的,但是read-7首先獲取到了cpu的執(zhí)行權(quán)限航揉,因而其先打印了其執(zhí)行代碼塞祈,而read-6則在獲取到cpu的執(zhí)行權(quán)限后輸出。

???????從上面的示例代碼可以看出迷捧,ReentrantReadWriteLock主要有四個(gè)方法:ReadLock.lock()织咧,ReadLock.unlock(),WriteLock.lock()和WriteLock.unlock()漠秋。這里ReadLock.lock()和ReadLock.unlock()是嘗試對(duì)讀鎖的獲取和釋放的笙蒙,WriteLock.lock()和WriteLock.unlock()是嘗試對(duì)寫鎖的獲取和釋放的。下面我們會(huì)對(duì)這四個(gè)方法分別進(jìn)行介紹庆锦,首先我們先來看看ReentrantReadWriteLock的類結(jié)構(gòu)圖:

???????從圖中可以看出捅位,ReadLock和WriteLock都是ReentrantReadWriteLock的內(nèi)部類,其主要是提供對(duì)寫鎖的鎖的獲取和釋放相關(guān)的接口的搂抒。首先我們看看幾個(gè)輔助的作用:

static final class NonfairSync extends Sync {
  private static final long serialVersionUID = -8159625535654395037L;
  final boolean writerShouldBlock() {
    return false;
  }
  final boolean readerShouldBlock() {
    return apparentlyFirstQueuedIsExclusive();
  }
}
static final class FairSync extends Sync {
  private static final long serialVersionUID = -2274990926593161451L;
  final boolean writerShouldBlock() {
    return hasQueuedPredecessors();
  }
  final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
  }
}

???????FairSync和NonfairSync兩個(gè)類用于表征當(dāng)前的讀寫鎖是公平的還是非公平的艇搀。非公平表示線程獲取讀鎖和寫鎖的概率是隨機(jī)的,也即誰先爭(zhēng)取到執(zhí)行權(quán)限就先執(zhí)行鎖定的代碼求晶;公平鎖表示線程所有線程獲取讀鎖和寫鎖的概率都是一樣的焰雕,當(dāng)線程嘗試獲取鎖的權(quán)限時(shí),其會(huì)被加入到一個(gè)等待隊(duì)列中芳杏,每次都是隊(duì)列頭部的線程最開始執(zhí)行鎖定代碼矩屁。一般的辟宗,公平鎖雖不能保證每個(gè)線程都有相同的機(jī)會(huì)執(zhí)行代碼,但是其能夠提高系統(tǒng)的吞吐率吝秕,但是其會(huì)提高造成死鎖的概率泊脐;公平鎖則能夠保證每個(gè)線程都有均等的機(jī)會(huì)執(zhí)行代碼,其也能降低造成死鎖的概率烁峭,但是其吞吐率沒有非公平鎖的高容客。

static final class HoldCounter {
  int count = 0;
  final long tid = getThreadId(Thread.currentThread());
}

???????HoldCounter是一個(gè)計(jì)數(shù)輔助類,其count字段記錄了每個(gè)線程重入讀鎖或?qū)戞i的次數(shù)约郁,tid字段則記錄了當(dāng)前是記錄的哪個(gè)線程缩挑。

static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
  public HoldCounter initialValue() {
    return new HoldCounter();
  }
}

???????ThreadLocalHoldCounter繼承自ThreadLocal,并且重寫了initialValue()方法棍现,也就是說每個(gè)線程使用該類時(shí)都有一個(gè)默認(rèn)的HoldCounter計(jì)數(shù)類调煎。這里ThreadLocalHoldCounter主要是為每個(gè)線程保存其計(jì)數(shù)器對(duì)象的。

???????ReentrantReadWriteLock內(nèi)部?jī)?nèi)部也是基于AbstractQueuedSynchronizer實(shí)現(xiàn)的己肮,這里Sync也就是該類的子類士袄。AbstractQueuedSynchronizer內(nèi)部是通過一個(gè)volatile類型的整型變量state來控制鎖的獲取的,這里volatile能夠保證多線程環(huán)境下該變量對(duì)多個(gè)線程都可見谎僻,而整型int占32個(gè)字節(jié)娄柳,無論是32位還是64位處理器,其都能夠保證每次處理的最小字節(jié)單元是32位艘绍,也就是說對(duì)該變量的更新操作都是原子的赤拒。AbstractQueuedSynchronizer主要提供了如下幾個(gè)方法:

public final void acquire(int arg);
public final void acquireShared(int arg);
protected int tryAcquire(int arg);
protected int tryAcquireShared(int arg);
public final boolean release(int arg);
public final boolean releaseShared(int arg);
protected boolean tryRelease(int arg);
protected boolean tryReleaseShared(int arg);

???????可以看到,這些方法都是成對(duì)呈現(xiàn)的诱鞠,AbstractQueuedSynchronizer使用了模板方法模式來實(shí)現(xiàn)對(duì)state屬性的控制的挎挖。這里acquire(int)和acquireShared(int)方法是對(duì)外的接口,分別表示以非共享或者共享的方式獲取鎖的執(zhí)行權(quán)限航夺。tryAcquire(int)和tryAcquireShared(int)方法則是提供的鉤子方法蕉朵,子類按照需要實(shí)現(xiàn)這兩個(gè)方法,這兩個(gè)方法分別是acquire(int)和acquireShared(int)方法調(diào)用的阳掐,用于控制當(dāng)前線程是否獲取到了鎖的執(zhí)行權(quán)限始衅,如果獲取到了,則當(dāng)前線程繼續(xù)執(zhí)行后續(xù)代碼缭保,如果沒有則均通過CAS算法獲取鎖的權(quán)限汛闸。同理,release(int)和releaseShared(int)方法也是對(duì)外提供的接口艺骂,分別表示以非共享和共享的方式釋放鎖的權(quán)限诸老。tryRelease(int)和tryReleaseShared(int)方法則也是鉤子方法,由子類實(shí)現(xiàn)钳恕,其分別是由release(int)和releaseShared(int)方法調(diào)用别伏,而其作用則分別為以非共享或者共享的方式釋放當(dāng)前鎖的執(zhí)行權(quán)限吮廉。首先我們看看ReentrantReadWriteLock內(nèi)部Sync的tryAcquire(int)方法的具體實(shí)現(xiàn):

protected final boolean tryAcquire(int acquires) {
  Thread current = Thread.currentThread();
  int c = getState();
  int w = exclusiveCount(c);    // 獲取當(dāng)前正在占用寫鎖的次數(shù)
  if (c != 0) { // c不等于0表示當(dāng)前肯定有讀鎖或者寫鎖被占用
    if (w == 0 || current != getExclusiveOwnerThread()) // 當(dāng)前沒有寫鎖占用或者占用寫鎖的不是當(dāng)前線程
      return false;
    if (w + exclusiveCount(acquires) > MAX_COUNT)   // 占用寫鎖的次數(shù)超過最大限制次數(shù)
      throw new Error("Maximum lock count exceeded");
    setState(c + acquires); // 走到這一步說明當(dāng)前線程是正在占用寫鎖的線程,那么成功獲取鎖的權(quán)限
    return true;
  }
  
  // 這里說明c==0畸肆,也即沒有線程占用鎖,那么判斷寫鎖是否應(yīng)該被阻塞宙址,并且判斷能否通過CAS算法設(shè)置state屬性值
  // 這里writerShouldBlock()也即前面講的FairSync和NoneFairSync中重寫的方法
  if (writerShouldBlock() ||
      !compareAndSetState(c, c + acquires))
    return false;
  setExclusiveOwnerThread(current); // 設(shè)置當(dāng)前獨(dú)占鎖的線程為當(dāng)前線程
  return true;
}

???????這里需要說明的是轴脐,由于Sync通過state屬性控制兩個(gè)鎖,這就需要兩種狀態(tài)抡砂,因而Sync將state屬性分為兩部分:高16位和低16位大咱。高16位的值指定了讀鎖的占用次數(shù),低16位的值則指定了寫鎖的占用次數(shù)注益,并且這里還可以推斷出碴巾,如果state不為0,而低16位等于0丑搔,那么說明高16位一定不為零厦瓢,也就是說當(dāng)前有線程正在占用讀鎖。為了對(duì)讀寫狀態(tài)的獲取方便啤月,Sync中聲明了幾個(gè)變量和相關(guān)的方法:

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

???????可以看出煮仇,sharedCount(int)方法用于獲取高16位的數(shù)值,exclusiveCount(int)用于獲取低16位的數(shù)值谎仲,并且這里MAX_COUNT表示讀鎖和寫鎖最多獲取的次數(shù)浙垫。

???????我們?cè)倩剡^頭來看tryAcquire(int)方法,該方法表示一個(gè)線程能否以獨(dú)占的方式獲取鎖的執(zhí)行權(quán)限郑诺,其首先判斷c是否不為0夹姥,然后在該if條件中判斷是否有線程占用讀鎖,或者是占用寫鎖的線程不是當(dāng)前線程辙诞,如果成立則表示當(dāng)前線程獲取寫鎖失敗辙售,這也就是為什么獲取了寫鎖的線程將會(huì)阻塞其余的讀線程和寫線程的原因;如果第一個(gè)條件不成立倘要,則說明當(dāng)前線程是已經(jīng)占用寫鎖的線程圾亏,那么就判斷已經(jīng)占用的次數(shù)(也即重入的次數(shù))加上此次占用的次數(shù)是否會(huì)超過最大占用次數(shù),如果不成立封拧,則表示當(dāng)前線程可以成功(再次)獲取寫鎖的權(quán)限志鹃,那么就設(shè)置state屬性的值為新的值,并返回true泽西。如果c為0曹铃,則說明當(dāng)前沒有線程占用讀鎖和寫鎖,那么就判斷寫鎖是否應(yīng)該被阻塞捧杉,或者能否通過CAS算法成功設(shè)置state為新的屬性值陕见。如果寫鎖不應(yīng)該被阻塞秘血,并且成功更新了state的值,那么表示當(dāng)前線程獲取寫鎖成功评甜,則設(shè)置獨(dú)占鎖的擁有者為當(dāng)前線程灰粮。這里需要說明的是,writerShouldBlock()方法是Sync的子類FairSync和NoneFairSync分別重寫了的方法忍坷。由前面的代碼可以看出粘舟,F(xiàn)airSync中該方法是判斷當(dāng)前阻塞隊(duì)列中是否有等待的線程,如果有等待的線程佩研,則返回true柑肴,也就表示當(dāng)前線程應(yīng)該被阻塞,因?yàn)楦鶕?jù)公平鎖的協(xié)議旬薯,線程獲取鎖的順序是到達(dá)的先后順序晰骑;在NoneFairSync中,writerShouldBlock()方法始終返回false绊序,表示在非公平鎖中硕舆,只要一個(gè)線程嘗試競(jìng)爭(zhēng)鎖的執(zhí)行權(quán)限,那么其就會(huì)不會(huì)被阻塞政模。這也就是公平鎖和非公平鎖的區(qū)別岗宣。

???????類似于tryAcquire(int)方法,tryAcquireShared(int)方法則表示當(dāng)前線程能否以共享的方式獲取鎖的執(zhí)行權(quán)限淋样。如下是該方法的代碼:

protected final int tryAcquireShared(int unused) {
  Thread current = Thread.currentThread();
  int c = getState();
  
  // 判斷是否有寫鎖存在耗式,或者是有寫鎖存在的情況下是否占用寫鎖的是當(dāng)前線程
  if (exclusiveCount(c) != 0 &&
      getExclusiveOwnerThread() != current)
    return -1;
  
  int r = sharedCount(c);
  // 判斷讀鎖是否應(yīng)該被阻塞,其次判斷占用讀鎖的次數(shù)是否超過最大限制趁猴,
  // 并且嘗試更新CAS算法設(shè)置state屬性的值為新的值
  if (!readerShouldBlock() &&
      r < MAX_COUNT &&
      compareAndSetState(c, c + SHARED_UNIT)) {
    if (r == 0) {
      firstReader = current;    // 記錄第一個(gè)獲取讀鎖的線程
      firstReaderHoldCount = 1; // 記錄第一個(gè)獲取讀鎖的線程重入次數(shù)
    } else if (firstReader == current) {
      firstReaderHoldCount++;   // 更新第一個(gè)獲取讀鎖的線程的重入次數(shù)
    } else {
      HoldCounter rh = cachedHoldCounter;   // 獲取緩存的線程的計(jì)數(shù)器
      // 如果緩存的計(jì)數(shù)器不為當(dāng)前線程的計(jì)數(shù)器不為當(dāng)前線程的計(jì)數(shù)器刊咳,則更新其為當(dāng)前線程的計(jì)數(shù)器
      if (rh == null || rh.tid != getThreadId(current))
        cachedHoldCounter = rh = readHolds.get();
      else if (rh.count == 0)   // 如果當(dāng)前線程計(jì)數(shù)器計(jì)數(shù)為0,則為其設(shè)置一個(gè)計(jì)數(shù)器
        readHolds.set(rh);
      rh.count++;   // 更新計(jì)數(shù)器的值儡司,即當(dāng)前線程的重入次數(shù)
    }
    return 1;
  }
  return fullTryAcquireShared(current);
}

???????從tryAcquireShared(int)方法可以看出娱挨,其首先判斷是否有線程占用寫鎖,并且如果有線程占用寫鎖捕犬,則判斷占用寫鎖的線程是否不為當(dāng)前線程跷坝,如果都不成立,則說明有其他的線程占用寫鎖碉碉,那么當(dāng)前線程就會(huì)被阻塞柴钻。這也就是讀寫鎖中鎖降級(jí)的原理,即一個(gè)鎖獲取讀鎖之后垢粮,在還沒有釋放鎖的情況下獲取寫鎖贴届,然后釋放讀鎖,這樣該線程就只占用一個(gè)讀鎖了,即從寫鎖降級(jí)為讀鎖毫蚓。這里的判斷當(dāng)前占用寫鎖的線程是否為其他線程就保證了當(dāng)前線程如果占用了寫鎖占键,那么其可以同時(shí)嘗試獲取讀鎖。在接下來的if條件中元潘,首先判斷readerShouldBlock()畔乙,即當(dāng)前讀線程是否應(yīng)該被阻塞,該方法也是FairSync和NoneFairSync中重寫的方法翩概,在FairSync中啸澡,其會(huì)判斷阻塞隊(duì)列中是否有阻塞的線程,如果有則當(dāng)前線程也應(yīng)該被阻塞氮帐,在NoneFairSync中,其會(huì)判斷當(dāng)前阻塞隊(duì)列中最近的被阻塞的節(jié)點(diǎn)是否為嘗試獲取寫鎖的線程洛姑,如果是上沐,則當(dāng)前線程應(yīng)該被阻塞。如果讀鎖不應(yīng)該被阻塞楞艾,并且當(dāng)前線程不會(huì)造成獲取讀鎖次數(shù)超限参咙,那么就會(huì)嘗試通過CAS算法更新state的值為新的值。在成功通過CAS算法設(shè)置state屬性值之后硫眯,說明當(dāng)前線程成功獲取到了讀鎖蕴侧,那么其就會(huì)更新記錄其重入次數(shù)的計(jì)數(shù)器。如果上述條件不成立两入,那么當(dāng)前線程就會(huì)通過無線for循環(huán)嘗試獲取鎖的權(quán)限净宵,具體的實(shí)現(xiàn)在fullTryAcquireShared(Thread)中:

final int fullTryAcquireShared(Thread current) {
  HoldCounter rh = null;
  for (;;) {
    int c = getState();
    if (exclusiveCount(c) != 0) {   // 判斷是否有寫鎖正在被占用
      if (getExclusiveOwnerThread() != current) // 判斷占用寫鎖的是否為當(dāng)前線程
        return -1;
    } else if (readerShouldBlock()) {   // 判斷讀鎖是否應(yīng)該被阻塞
      if (firstReader == current) {
      } else {  // 走到這一步說明沒有寫鎖占用,或者是占用寫鎖的線程為當(dāng)前線程裹纳,并且讀鎖不應(yīng)該被阻塞择葡,
        if (rh == null) {
          rh = cachedHoldCounter;
          if (rh == null || rh.tid != getThreadId(current)) {
            rh = readHolds.get();
            if (rh.count == 0)  // 如果重入次數(shù)為0,說明其當(dāng)前是沒有占用讀鎖的狀態(tài)
              readHolds.remove();
          }
        }
        if (rh.count == 0)  // 當(dāng)前線程沒有占用讀鎖剃氧,因?yàn)樘幱趓eaderShouldBlock()塊中敏储,因而應(yīng)該被阻塞
          return -1;
      }
    }
    if (sharedCount(c) == MAX_COUNT)
      throw new Error("Maximum lock count exceeded");
    if (compareAndSetState(c, c + SHARED_UNIT)) {   // 使用CAS算法更新state屬性的值
      if (sharedCount(c) == 0) {
        firstReader = current;  // 記錄第一個(gè)讀線程
        firstReaderHoldCount = 1;   // 記錄第一個(gè)讀線程的重入次數(shù)
      } else if (firstReader == current) {
        firstReaderHoldCount++; // 更新第一個(gè)讀線程的重入次數(shù)
      } else {
        if (rh == null)
          rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
          rh = readHolds.get();
        else if (rh.count == 0)
          readHolds.set(rh);
        rh.count++; // 更新當(dāng)前線程的重入次數(shù)
        cachedHoldCounter = rh; // 緩存當(dāng)前讀線程的計(jì)數(shù)器
      }
      return 1;
    }
  }
}

???????可以看出tryAcquireShared(int)和fullTryAcquireShared(Thread)方法的區(qū)別在于tryAcquireShared(int)方法只會(huì)對(duì)當(dāng)前線程嘗試一次是否能夠獲取到讀鎖的權(quán)限,其不一定能夠保證當(dāng)前線程獲取到讀鎖朋鞍,而fullTryAcquireShared(Thread)方法則在一個(gè)無限for循環(huán)中嘗試獲取讀鎖已添,其能夠保證在沒有寫鎖或者寫鎖不為當(dāng)前鎖的情況下當(dāng)前線程一定能夠獲取到讀鎖。這里也可以看出滥酥,在不存在寫鎖的情況下更舞,如果是多個(gè)線程嘗試競(jìng)爭(zhēng)讀鎖,那么每個(gè)線程都會(huì)進(jìn)入無限for循環(huán)中恨狈,并且保證一定能夠獲取到讀鎖疏哗,此時(shí)CPU是一直在運(yùn)轉(zhuǎn)的,相較于AbstractQueuedSynchronizer.doAcquireShared(int)方法,如果當(dāng)前線程沒有競(jìng)爭(zhēng)到讀鎖返奉,那么其也會(huì)進(jìn)入無限for循環(huán)贝搁,但是在循環(huán)一次之后其就會(huì)被操作系統(tǒng)“擱置”起來,從而釋放資源芽偏。ReentrantReadWriteLock的這種競(jìng)爭(zhēng)讀鎖的機(jī)制能夠保證線程以最快的方式獲取到讀鎖雷逆,而不必被“擱置”,從而造成不必要的線程環(huán)境切換污尉。

???????上面講解的tryAcquire(int)和tryAcquireShared(int)方法分別被獲取寫鎖的線程和獲取讀鎖的線程調(diào)用膀哲,分別表示能否成功以獨(dú)占的方式和以共享的方式獲取寫鎖和讀鎖的權(quán)限。接下來我們來看看tryRelease(int)和tryReleaseShared(int)方法被碗,這兩個(gè)方法分別表示能否成功以獨(dú)占的方式和以共享的方式釋放寫鎖和讀鎖的權(quán)限某宪。如下是tryRelease(int)方法的源碼:

protected final boolean tryRelease(int releases) {
  if (!isHeldExclusively()) // 判斷當(dāng)前線程是否為以獨(dú)占方式占用寫鎖權(quán)限的線程
    throw new IllegalMonitorStateException();
  int nextc = getState() - releases;    // 記錄釋放寫鎖后state屬性的值
  boolean free = exclusiveCount(nextc) == 0;    // 判斷釋放寫鎖后是否還存在寫鎖
  if (free)
    setExclusiveOwnerThread(null);  // 如果不存在寫鎖,則清楚獨(dú)占鎖的線程狀態(tài)
  setState(nextc);  // 設(shè)置state屬性的值
  return free;
}

???????可以看到锐朴,在tryRelease(int)方法中兴喂,其首先判斷當(dāng)前線程是否為以獨(dú)占方式占用(寫)鎖的線程,如果不是則其沒有權(quán)限釋放鎖焚志。接著主要是對(duì)state屬性的值進(jìn)行相應(yīng)的更新衣迷。這里需要注意的是,在free變量的判斷中酱酬,使用的是exclusiveCount(nextc)是否為0壶谒,其實(shí)可以理解,當(dāng)前以獨(dú)占方式占用寫鎖的線程膳沽,如果其沒有同時(shí)占用讀鎖汗菜,那么其高位部分肯定都是0,因而只需要判斷nextc是否為0即可挑社,這里去除高位部分呵俏,只判斷低位部分是否為0的原因就是當(dāng)前線程可能既同時(shí)占用寫鎖又占用了讀鎖,因而不能簡(jiǎn)簡(jiǎn)單單的判斷nextc是否為0滔灶。接下來我們看看tryReleaseShared(int)方法普碎,以下是該方法的實(shí)現(xiàn):

protected final boolean tryReleaseShared(int unused) {
  Thread current = Thread.currentThread();
  if (firstReader == current) { // 如果當(dāng)前線程為第一個(gè)更新state屬性的線程,則維護(hù)相關(guān)的變量值
    if (firstReaderHoldCount == 1)
      firstReader = null;
    else
      firstReaderHoldCount--;
  } else {
    HoldCounter rh = cachedHoldCounter;
    if (rh == null || rh.tid != getThreadId(current))   // 獲取當(dāng)前線程的計(jì)數(shù)器
      rh = readHolds.get();
    int count = rh.count;
    if (count <= 1) {   // 如果計(jì)數(shù)器的計(jì)數(shù)值小于等于1录平,說明釋放后其會(huì)至少變?yōu)?麻车,因而清除計(jì)數(shù)器相關(guān)信息
      readHolds.remove();
      if (count <= 0)
        throw unmatchedUnlockException();
    }
    --rh.count; // 更新計(jì)數(shù)器的值
  }
  for (;;) {
    int c = getState();
    int nextc = c - SHARED_UNIT;
    if (compareAndSetState(c, nextc))   // 以CAS算法更新state屬性的值
      return nextc == 0;
  }
}

???????可以看到,tryReleaseShared(int)方法首先判斷當(dāng)前線程是否為將state屬性從0更新為1的線程斗这,如果是动猬,則更新相關(guān)的屬性值,否則對(duì)當(dāng)前線程的計(jì)數(shù)器信息進(jìn)行更新表箭。更新完相關(guān)屬性值之后赁咙,當(dāng)前線程會(huì)進(jìn)入一個(gè)無線for循環(huán),并且以CAS算法更新state屬性的值,這樣可以保證一定能夠以原子的形式將state更新成功彼水。這里返回值為nextc是否為0崔拥,這是因?yàn)楫?dāng)前線程可能重入當(dāng)前鎖多次,也就是說如果當(dāng)前線程還沒有完全釋放當(dāng)前鎖的時(shí)候凤覆,其是不會(huì)執(zhí)行后續(xù)喚醒后續(xù)線程中的代碼的链瓦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盯桦,隨后出現(xiàn)的幾起案子慈俯,更是在濱河造成了極大的恐慌,老刑警劉巖拥峦,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贴膘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡略号,警方通過查閱死者的電腦和手機(jī)步鉴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來璃哟,“玉大人,你說我怎么就攤上這事喊递∷嫔粒” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵骚勘,是天一觀的道長(zhǎng)铐伴。 經(jīng)常有香客問我,道長(zhǎng)俏讹,這世上最難降的妖魔是什么当宴? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮泽疆,結(jié)果婚禮上户矢,老公的妹妹穿的比我還像新娘。我一直安慰自己殉疼,他們只是感情好梯浪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瓢娜,像睡著了一般挂洛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眠砾,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天虏劲,我揣著相機(jī)與錄音,去河邊找鬼。 笑死柒巫,一個(gè)胖子當(dāng)著我的面吹牛励堡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吻育,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼念秧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了布疼?” 一聲冷哼從身側(cè)響起摊趾,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎游两,沒想到半個(gè)月后砾层,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贱案,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年肛炮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宝踪。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侨糟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瘩燥,到底是詐尸還是另有隱情秕重,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布厉膀,位于F島的核電站溶耘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏服鹅。R本人自食惡果不足惜凳兵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望企软。 院中可真熱鬧庐扫,春花似錦、人聲如沸仗哨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽藻治。三九已至碘勉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桩卵,已是汗流浹背验靡。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工倍宾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胜嗓。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓高职,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親辞州。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怔锌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容