多線程基礎(chǔ)(十四):AbstractQueuedSynchronizer源碼分析

[toc]

1.類結(jié)構(gòu)及成員變量

1.1 類結(jié)構(gòu)和注釋

類AbstractQueuedSynchronizer是java并發(fā)包中的核心,是實(shí)現(xiàn)大部分并發(fā)工具類的底層工具類,現(xiàn)在對這個(gè)類的源碼進(jìn)行分析。

1.1.1 類結(jié)構(gòu)

AbstractQueuedSynchronizer類的繼承結(jié)構(gòu)如下,其中AbstractOwnableSynchronizer是一個(gè)抽象類,其中只定義了部分需要實(shí)現(xiàn)的抽象方法。


類結(jié)構(gòu)

其內(nèi)部有兩個(gè)核心的內(nèi)部類,Node和ConditionObject。
需要注意的是,AbstractQueueSynhronizer類本身就是一個(gè)抽象類,其他的同步工具如果要使用AQS都需要先繼承。

1.1.2 注釋

在類前面的注釋部分如下:
提供了一個(gè)用于實(shí)現(xiàn)依賴于FIFO等待隊(duì)列的阻塞鎖和相關(guān)的同步器(實(shí)現(xiàn)了信號燈、事件等)。此類旨在為大多數(shù)依賴單個(gè)原子的同步器提供基礎(chǔ)操作。子類必須定義更改此類狀態(tài)的受保護(hù)的方法,并定義該狀態(tài)對于獲取或者釋放此對象而言意味著什么。鑒于這些原因,此類中的其他方法將執(zhí)行所有的排隊(duì)和阻塞機(jī)制,子類可以維護(hù)其他狀態(tài)字段。但僅跟蹤使用方法getState、setState、compareAndSetState進(jìn)行原子更新的int的值的同步性。
子類應(yīng)定義為用于實(shí)現(xiàn)其所在類的同步屬性的非公共內(nèi)部幫助器類,AbstractQueuedSynchronizer沒有實(shí)現(xiàn)任何同步接口,相反,它定義了acquireInterruptible之類的方法,可以通過具體的鎖和相關(guān)的同步器適當(dāng)?shù)恼{(diào)用這些方法來實(shí)現(xiàn)其公共方法。
此類支持默認(rèn)的exclusive模式和shared模式。當(dāng)以獨(dú)立方式進(jìn)行獲取時(shí),其他線程嘗試進(jìn)行獲取不會成功。由多個(gè)線程獲取的共享模式可能成功。此類并不理解這些機(jī)械上的區(qū)別,即當(dāng)成功獲取共享模式時(shí),下一個(gè)等待線程如果存在,還必須確定它是否也可以獲取。在不同模式下等待線程共享相同的FIFO隊(duì)列。通常,實(shí)現(xiàn)子類僅支持這些模式之一,但如果可以在ReadWriteLock中發(fā)揮作用。僅支持互斥模式或僅共享模式的子類無需定義支持未使用的模式方法。
此類定義了一個(gè)內(nèi)部類ConditionObject,用以支持獨(dú)占模式的子類用作Condition實(shí)現(xiàn)。為isHeldExclusively方法報(bào)告是否針對當(dāng)前線程專有的保留同步。使用當(dāng)前getState值調(diào)用的方法,會完全釋放此對象,并且給定已保存的狀態(tài)值,acquire最終會將其恢復(fù)為先前的獲取狀態(tài)。否則,沒有AbstractQueuedSynchronizer方法會創(chuàng)建這樣的條件,因此,如果無法滿足此約束,請不要使用它。ConditionObject的行為當(dāng)然取決于其同步器實(shí)現(xiàn)的語義。
此類提供了內(nèi)部隊(duì)列的檢查、檢測和監(jiān)視方法。可以根據(jù)需要使用AbstractQueuedSynchronizer將他們導(dǎo)出到類中以實(shí)現(xiàn)其同步機(jī)制。
此類的序列化僅存儲基本的原子整數(shù)維護(hù)狀態(tài),因此,反序列化的對象的隊(duì)列是空的,需要可序列化的典型子類將定義一個(gè)readObject方法,該方法在反序列化的時(shí)候?qū)⑵浠謴?fù)為已知的狀態(tài)。
要將此類用作同步器的基礎(chǔ),請使用getState,或者setState和compareAndSetState。檢查或修改同步狀態(tài),重新定義以下方法:

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively
    默認(rèn)情況下,這些方法中的每一個(gè)都會引發(fā)UnsupportedOperationException。這些方法必須在內(nèi)部是線程安全的,并且通常應(yīng)該簡短而不阻塞。定義這些方法是only 支持使用此類的方法。所有其他方法都申明為final,因?yàn)樗鼈儾荒塥?dú)立變化。
    你可能還會發(fā)現(xiàn),從AbstractOwnableSynchronizer繼承的方法對于跟蹤擁有獨(dú)占同步器的線程很有用。鼓勵(lì)你使用它們,這將啟用監(jiān)視和診斷工具。以幫助用戶確定哪些線程持有鎖。
    即使此類基于FIFO隊(duì)列,它們也不會自動執(zhí)行FIFO獲取策略,獨(dú)占同步的核心采用以下形式:
Acquire:
    while (!tryAcquire(arg)) {
       將線程入隊(duì)
       阻塞當(dāng)前線程
    }
 Release:
    if (tryRelease(arg))
        取消阻塞第一個(gè)排隊(duì)的線程

共享模式與此類似,但可能涉及級聯(lián)信號。

因?yàn)楂@取隊(duì)列中的獲取檢查是在排隊(duì)之前被調(diào)用的,所以新獲取的線程可能在被阻塞和排隊(duì)的其他線程之前插入。但是,如果需要,你可以定義tryAcquire或tryAcquireShared以通過內(nèi)部調(diào)用的一種或者多種檢查方法來禁用插入,從而提供一個(gè)fair FIFO獲取順序。特別是,如果hasQueuedPredecessors(一種專門為公平同步器設(shè)計(jì)的方法)返回true,則大多數(shù)公平器都可以定義tryAcquire返回false。其他變化是可能的。

對于默認(rèn)插入,greedy、renouncement、convoy-avoidance策略,吞吐量的可伸縮性通常最高。盡管不能保證鎖的公平性,也可以避免饑餓,但是允許在較早排隊(duì)的線程之前對較早排隊(duì)的線程進(jìn)行重新競爭,并且每個(gè)重新爭用都可以毫無偏向地成功抵御傳入線程。而且,盡管獲取不“旋轉(zhuǎn)”在通常的意義上,它們可以在阻塞之前執(zhí)行{@code tryAcquire}的多次調(diào)用并插入其他計(jì)算。如果僅短暫地保持排他同步,則這將帶來旋轉(zhuǎn)的大部分好處,而在沒有同步時(shí),則不會帶來很多負(fù)擔(dān)。如果需要的話,您可以通過在調(diào)用之前使用“快速路徑”檢查來獲取方法來增強(qiáng)此功能,可能會預(yù)先檢查hasContended和/或hasQueuedThreads以僅在同步器可能不這樣做的情況下這樣做爭辯。
此類為同步提供了有效且可擴(kuò)展的基礎(chǔ),部分原因是通過將其使用范圍專門用于可以依靠int狀態(tài),獲取和釋放參數(shù)以及內(nèi)部FIFO等待隊(duì)列的同步器。如果這還不夠,則可以使用java.util.concurrent.atomic類,您自己的自定義 java.util.Queue類和LockSupport較低級別構(gòu)建同步器支持阻塞。

使用范例

這是一個(gè)不可重入的互斥鎖定類,使用值0表示解鎖狀態(tài),使用值1表示鎖定狀態(tài)。雖然不可重入鎖并不嚴(yán)格要求記錄當(dāng)前所有者線程,但是無論如何,此類都這樣做以使使用情況更易于監(jiān)視。它還支持條件并公開一種檢測方法:

class Mutex implements Lock, java.io.Serializable {
 
// Our internal helper class
private static class Sync extends AbstractQueuedSynchronizer {
  // Reports whether in locked state
  protected boolean isHeldExclusively() {
    return getState() == 1;
  }

  // Acquires the lock if state is zero
  public boolean tryAcquire(int acquires) {
    assert acquires == 1; // Otherwise unused
    if (compareAndSetState(0, 1)) {
      setExclusiveOwnerThread(Thread.currentThread());
      return true;
    }
    return false;
  }

  // Releases the lock by setting state to zero
  protected boolean tryRelease(int releases) {
    assert releases == 1; // Otherwise unused
    if (getState() == 0) throw new llegalMonitorStateException();
   setExclusiveOwnerThread(null);
    setState(0);
    return true;
  }

  // Provides a Condition
  Condition newCondition() { return new ConditionObject(); }

  // Deserializes properly
  private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    setState(0); // reset to unlocked state
  }
} 
// The sync object does all the hard work. We just forward to it.
   private final Sync sync = new Sync();

   public void lock()                { sync.acquire(1); }
   public boolean tryLock()          { return sync.tryAcquire(1); }
   public void unlock()              { sync.release(1); }
   public Condition newCondition()   { return sync.newCondition(); }
   public boolean isLocked()         { return sync.isHeldExclusively(); }
   public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
   public void lockInterruptibly() throws InterruptedException {
     sync.acquireInterruptibly(1);
   }
   public boolean tryLock(long timeout, TimeUnit unit)
       throws InterruptedException {
     return sync.tryAcquireNanos(1, unit.toNanos(timeout));
   }
}}

這是一個(gè)類似于CountDownLatch的類,只不過,它需要觸發(fā)一個(gè)signal。由于這個(gè)鎖是非排他性,因此使用shared獲取和釋放方法。

 class BooleanLatch {

   private static class Sync extends AbstractQueuedSynchronizer {
     boolean isSignalled() { return getState() != 0; }
     protected int tryAcquireShared(int ignore) {
       return isSignalled() ? 1 : -1;
     }

     protected boolean tryReleaseShared(int ignore) {
       setState(1);
       return true;
     }
   }

   private final Sync sync = new Sync();
   public boolean isSignalled() { return sync.isSignalled(); }
   public void signal()         { sync.releaseShared(1); }
   public void await() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
   }
 }}

1.2 成員變量及常量

與大多數(shù)數(shù)據(jù)結(jié)構(gòu)復(fù)雜的集合類不同的是,AbstractQueuedSynchronizer類并沒有用特別復(fù)雜的數(shù)據(jù)結(jié)構(gòu)和二進(jìn)制的控制字段。其主要的數(shù)據(jù)結(jié)構(gòu)是一個(gè)鏈表。變量及常量如下:

/**
 * Head of the wait queue, lazily initialized.  Except for
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be
 * CANCELLED.
 */
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via
 * method enq to add new wait node.
 */
private transient volatile Node tail;

/**
 * The synchronization state.
 */
private volatile int state;

/**
 * Returns the current value of synchronization state.
 * This operation has memory semantics of a {@code volatile} read.
 * @return current state value
 */
 
 /**
 * The number of nanoseconds for which it is faster to spin
 * rather than to use timed park. A rough estimate suffices
 * to improve responsiveness with very short timeouts.
 */
static final long spinForTimeoutThreshold = 1000L;

上述內(nèi)容見下表所示:

變量名 類型 說明
head private transient volatile Node 表示等待隊(duì)列的頭部,除初始化方法之外,只能通過setHead方法進(jìn)行修改,需要注意的是,如果head存在,則保證waitStatus不會變成CANCELLED狀態(tài)。
tail private transient volatile Node 表示等待隊(duì)列的尾部,只能通過enq方法進(jìn)行修改以添加新的等待節(jié)點(diǎn)
state private volatile int 表示同步的狀態(tài)
spinForTimeoutThreshold static final long 采用納秒數(shù)作為park的等待時(shí)間,這樣可以大幅提高響應(yīng)能力,這個(gè)默認(rèn)值為1000L,單位為納秒,這也是AQS類中的唯一一個(gè)常量。

2.構(gòu)造方法

protected AbstractQueuedSynchronizer() { }

默認(rèn)是一個(gè)空的構(gòu)造方法,此使的state狀態(tài)為0。
我們可以參考ThreadPoolExecutor類中的內(nèi)部類Worker,其在繼承AbstractQueuedSynchronizer之后,在其構(gòu)造方法中通過 setState(-1)來設(shè)置AQS state的狀態(tài)。

3.關(guān)鍵的內(nèi)部類

3.1 Node

這是標(biāo)識等待隊(duì)列的節(jié)點(diǎn)類。

3.1.1 注釋

這個(gè)類是CLH(Craig, Landin, and Hagersten)鎖定隊(duì)列的變體,CLH的鎖通常用于自旋,相反,我們在AQS中則用于實(shí)現(xiàn)阻塞同步器,即使相同的策略,即將有關(guān)線程的某些控制信息保存在其節(jié)點(diǎn)中。每個(gè)節(jié)點(diǎn)中的status將跟蹤線程是否應(yīng)該阻塞,節(jié)點(diǎn)的前節(jié)點(diǎn)釋放時(shí)會法出信號,否則,隊(duì)列中的每個(gè)節(jié)點(diǎn)都充當(dāng)一個(gè)特定通知樣式的監(jiān)視器,其中包含一個(gè)等待線程,雖然狀態(tài)字段不控制是否授予線程鎖定,線程可能會嘗試獲取它是否在隊(duì)列中的第一位,但是并不能保證成功,它只是賦予了競爭權(quán),因此,當(dāng)前release的最新的等待線程可能需要重新等待。
如果需要加入clh鎖,你可以自動將其作為新的尾部入隊(duì),如果需要出隊(duì),只需要對head指針進(jìn)行設(shè)置。

      +------+  prev +-----+       +-----+
 head |      | <---- |     | <---- |     |  tail
      +------+       +-----+       +-----+

插入到CLH隊(duì)列中只需要對tail指針進(jìn)行一次原子操作,因此,從未排隊(duì)到排隊(duì)都有一個(gè)簡單的原子型的分界點(diǎn),同樣,出隊(duì)僅涉及更新頭,但是,節(jié)點(diǎn)需要花費(fèi)更多的精力來確定其后繼者是誰,部分原因是要處理由于超時(shí)和中斷而可能導(dǎo)致的取消操作。
prev鏈接在原始CLH鎖中未使用,主要用于處理取消,如果取消某個(gè)節(jié)點(diǎn),其后繼節(jié)點(diǎn)通常會重新鏈接到未取消的前任節(jié)點(diǎn),有關(guān)自旋鎖情況下類似機(jī)制的說明,請參見Scott和Scherer的論文High-Performance Synchronization for Shared-Memory Parallel Programs
我們還使用next指針來實(shí)現(xiàn)阻塞機(jī)制,每個(gè)節(jié)點(diǎn)的線程ID都保留在其自身的節(jié)點(diǎn)中,因此前一個(gè)節(jié)點(diǎn)通過遍歷下一個(gè)鏈接以確定它是哪個(gè)線程,從而通知和喚醒這個(gè)線程,確定后繼節(jié)點(diǎn)必須避免與新排隊(duì)的節(jié)點(diǎn)競爭來設(shè)置其前任節(jié)點(diǎn)的next字段,如果需要,可以通過在節(jié)點(diǎn)的后繼者為空時(shí),從原子更新的tail向后檢查來解決此問題。(換句話說,下一個(gè)鏈接是一種優(yōu)化,因此我們通常不需要向后掃描)
取消操作將一些保守性質(zhì)引入了基本算法,由于我們必須對其他節(jié)點(diǎn)是否取消進(jìn)行輪詢,因此我們可能會遺漏沒有注意到已取消的節(jié)點(diǎn)在我們前面或者后面,要解決這個(gè)問題,必須始終在取消時(shí)對后繼者也取消,使得他們能夠穩(wěn)定在新的前任者之上,除非我們能夠確定誰將擔(dān)負(fù)這一責(zé)任的前任節(jié)點(diǎn)。
CLH隊(duì)列需要一個(gè)虛擬標(biāo)頭節(jié)點(diǎn)才能開始。但是我們不會在構(gòu)建過程中創(chuàng)建它們,因?yàn)槿绻麤]有爭用,這將是浪費(fèi)時(shí)間。而是構(gòu)造節(jié)點(diǎn),并在第一次爭用時(shí)設(shè)置頭和尾指針。
等待條件變量的線程使用相同的節(jié)點(diǎn),但使用附加鏈接。條件只需要在簡單(非并行)鏈接隊(duì)列中鏈接節(jié)點(diǎn),因?yàn)閮H當(dāng)它們專用時(shí)才可以訪問它們。等待時(shí),將節(jié)點(diǎn)插入條件隊(duì)列。收到信號后,該節(jié)點(diǎn)將轉(zhuǎn)移到主隊(duì)列。狀態(tài)字段的特殊值用于標(biāo)記節(jié)點(diǎn)所在的隊(duì)列。
感謝Dave Dice,Mark Moir,Victor Luchangco,Bill Scherer和Michael Scott以及JSR-166專家組的成員,對本課程的設(shè)計(jì)提出了有益的想法,討論和批評。

3.1.2 常量

/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;

/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
 * waitStatus value to indicate the next acquireShared should
 * unconditionally propagate
 */
static final int PROPAGATE = -3;

上述常量整理為下表:

名稱 類型 說明
SHARED static final Node 示節(jié)點(diǎn)在共享模式下等待的標(biāo)記
EXCLUSIVE static final Node 指示節(jié)點(diǎn)以排他模式等待的標(biāo)記
CANCELLED static final int waitStatus值表示線程已被取消,int值為1
SIGNAL static final int SIGNAL 表示后續(xù)線程需要執(zhí)行unpark,值為-1
CONDITION static final int 表示線程正在等待狀態(tài),值為-2
PROPAGATE static final int 表示下一個(gè)節(jié)點(diǎn)獲得共享狀態(tài)應(yīng)該無條件的被傳播

3.1.3 變量

3.1.3.1 waitStatus

有關(guān)status狀態(tài)字段的取值說明:

狀態(tài) 說明
SIGNAL 表示該節(jié)點(diǎn)的后續(xù)節(jié)點(diǎn)將被阻塞(或者很快將要,通過park方法),因此當(dāng)前節(jié)點(diǎn)釋放或者取消的時(shí)候,必須對其后續(xù)節(jié)點(diǎn)unpark,為了避免沖突,acquire方法必須首先指示他們需要的信號,然后重新進(jìn)行原子型的獲取,然后在失敗的時(shí)候阻塞
CANCELLED 由于超時(shí)或者中斷導(dǎo)致該節(jié)點(diǎn)被取消,節(jié)點(diǎn)永遠(yuǎn)不會離開這個(gè)狀態(tài),具有取消的節(jié)點(diǎn),永遠(yuǎn)不會在此被阻塞
CONDITION 該節(jié)點(diǎn)當(dāng)前在條件隊(duì)列中,在傳輸之前,它不會用作同步隊(duì)列節(jié)點(diǎn),此狀態(tài)將設(shè)置為0,此值的使用與該字段的其他狀態(tài)無關(guān),對該機(jī)制進(jìn)行了簡化
PROPAGATE releaseShared應(yīng)該傳播到其他節(jié)點(diǎn),在doReleaseShared對此進(jìn)行了設(shè)置,僅適用于頭節(jié)點(diǎn),以確保傳播繼續(xù)進(jìn)行,即使此后進(jìn)行了其他操作也是如此。
0 不是以上任何一種情況

這些值以數(shù)字的方式排列以簡化使用,非負(fù)值表示節(jié)點(diǎn)不需要發(fā)信號,因此,大多數(shù)代碼不需要檢查特定值,僅需檢查符號即可。對于常規(guī)的同步節(jié)點(diǎn),該字段初始化為0,對于條件節(jié)點(diǎn),該字段初始化為CONDITION,使用CAS(或者在可能的情況下進(jìn)行無條件的volatile寫操作)進(jìn)行修改。

3.1.3.2 其他變量

其他變量見下表:

變量 類型 說明
prev volatile Node 鏈接到當(dāng)前節(jié)點(diǎn)/線程用來檢查waitStatus的先前節(jié)點(diǎn)。在入隊(duì)期間分配,并且僅在出隊(duì)的時(shí)候?qū)⑵淝蹇眨ǔ鲇贕C的考慮),同樣,在取消前任后,我們會短路,同時(shí)找到一個(gè)未取消的前任,這將一直存在,因?yàn)楦?jié)點(diǎn)永遠(yuǎn)不會被取消,只有成功獲取之后,節(jié)點(diǎn)才變成根。被取消的線程永遠(yuǎn)不會成功獲取,并且一個(gè)線程只會取消自身,而不會取消任何其他節(jié)點(diǎn)。
next volatile Node 鏈接到后繼節(jié)點(diǎn),當(dāng)前節(jié)點(diǎn)/線程在釋放時(shí)將其解散,在排隊(duì)過程中分配,在繞過取消的前任對象時(shí)進(jìn)行調(diào)整,在出隊(duì)時(shí)無效(出于對GC的考慮)。enq操作,直到附加后才分配前任節(jié)點(diǎn)的下一個(gè)字段,因此看到空的下一個(gè)字段不一定表示節(jié)點(diǎn)在隊(duì)列的末尾。但是,如果下一個(gè)字段可能為空,則我們可以從尾部掃描上一個(gè)以進(jìn)行再次檢查,被取消節(jié)點(diǎn)的下一個(gè)字段設(shè)置指向節(jié)點(diǎn)本身而不是null,以使得isOnSyncQueue的工作更輕松
thread volatile Thread 使該節(jié)點(diǎn)排隊(duì)的線程。在構(gòu)造上初始化,使用后消失。
nextWaiter Node 鏈接到等待條件的下一個(gè)節(jié)點(diǎn),或者鏈接到特殊值SHARED。由于條件隊(duì)列僅在以獨(dú)占模式保存時(shí)才被訪問,因此我們只需要一個(gè)簡單的鏈表隊(duì)列即可以在節(jié)點(diǎn)等待條件時(shí)保存節(jié)點(diǎn),然后他們在轉(zhuǎn)移到隊(duì)列的過程中以重新獲取。由于條件只能是互斥的,因此我們使用特殊值來表示共享模式來保存字段。

3.1.4 構(gòu)造函數(shù)

Node是AQS的基本單元,其構(gòu)成了AQS的等待隊(duì)列和Condition的條件隊(duì)列。結(jié)構(gòu)如下:


Node結(jié)構(gòu)

Node提供的構(gòu)造函數(shù)一共有3種,分別是用于初始化和添加waiter,以及通過Condation。

3.1.4.1 Node()
Node() {    // Used to establish initial head or SHARED marker
}
3.1.4.2 Node(Thread thread, Node mode)

此方法用于添加waiter,只需要初始化thread,node。

Node(Thread thread, Node mode) {     // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
}
3.1.4.3 Node(Thread thread, int waitStatus)

此方法用于Condation,初始化thread、waitStatus。

Node(Thread thread, int waitStatus) { // Used by Condition
    this.waitStatus = waitStatus;
    this.thread = thread;
}

3.2 ConditionObject

ConditionObject的實(shí)現(xiàn)也是一個(gè)隊(duì)列,實(shí)際上其內(nèi)部是由Node組成的鏈表。在這個(gè)類種定義了首尾兩個(gè)指針。|

/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
變量名稱 類型 說明
firstWaiter private transient Node 條件隊(duì)列的第一個(gè)節(jié)點(diǎn)。
lastWaiter private transient Node 條件隊(duì)列的最后一個(gè)節(jié)點(diǎn)。

其結(jié)構(gòu)如下:


ConditionObject結(jié)構(gòu)

ConditionObject主要是用于ReentrantLock等鎖對象的時(shí)候,作為派生的條件變量Condition。一個(gè)Lock可以通過newCondition方法,派生出多個(gè)Condition對象。而一個(gè)Condition對象就是一個(gè)隊(duì)列,此使復(fù)用了AQS類種的Node節(jié)點(diǎn),這個(gè)Condition實(shí)際上只是用到了nextWaiter指針,是一個(gè)單向鏈表結(jié)構(gòu)。
這樣構(gòu)成的Condition隊(duì)列如下:


Condition隊(duì)列

由于ConditionObject的方法較為復(fù)雜,不在本文中詳細(xì)描述,后面單獨(dú)來講。

4.基本原理

AbstractQueueSynchronizer是java并發(fā)包中的核心部分,大多數(shù)同步工具,如ReentrantLock、CountDownLantch等都是構(gòu)建在AbustactQueueSynchronizer之上的應(yīng)用對象。因此,理解好AbstaractQueueSynchronizer之后,有利于理解并發(fā)包中的其他應(yīng)用類。
經(jīng)過前面的描述,我們可以直到AQS的基本構(gòu)成:


AQS隊(duì)列

AQS實(shí)際上是一個(gè)雙向鏈表組成的隊(duì)列,Node是AQS的基本構(gòu)成節(jié)點(diǎn),其內(nèi)部指針通過prev和next來雙向描述鏈表,而nextWaiter指針則專門用于Condition。每個(gè)Condition會有一個(gè)單獨(dú)的隊(duì)列。
在傳統(tǒng)的理解上,我們總將synhronized與ReentrantLock進(jìn)行等價(jià),認(rèn)為ReentrantLock就是與synchronized類似的鎖,實(shí)際上,ReentrantLock依賴于AQS來實(shí)現(xiàn),而AQS本身并不是什么鎖。AQS采用標(biāo)記狀態(tài)+隊(duì)列來實(shí)現(xiàn),記錄獲取鎖、競爭鎖、釋放鎖的一系列操作,其并不關(guān)心什么是鎖,而是采用了一些列判斷資源是否可以訪問的API,并且對訪問資源受限的時(shí)候,對請求線程的操作進(jìn)行封裝,如加入隊(duì)列、掛起、喚醒等操作。對于線程的操作將采用LockSupport的park和unpark方法。在前面學(xué)習(xí)LockSupport的時(shí)候?qū)W過,LockSupport底層是使用的UnSafe類提供的方法。而AQS本身也大量采用了UnSafe提供的底層API實(shí)現(xiàn),這體現(xiàn)在CAS操作之上。
對于AQS,我們需要關(guān)心三類問題。

  • 資源的訪問方式,是同時(shí)支持多個(gè)線程訪問,還是只能允許一個(gè)線程訪問?
  • 資源如果訪問的時(shí)候無法獲得,將如何處理?
  • 如果有線程等待的時(shí)間過長,不想繼續(xù)等待,又將如何處理?

這些問題將是我們學(xué)習(xí)AQS的一些基本思路。對此,關(guān)于支持多線程還是單線程訪問的問題,這樣就有了獨(dú)占和共享兩種模式,AQS分別對于獨(dú)占和共享提供了相關(guān)的API方法,而其子類,要么實(shí)現(xiàn)了獨(dú)占,如ReentrantLock,要么實(shí)現(xiàn)了共享如ReentrantReadWriteLock。任何一個(gè)子類都不會同時(shí)實(shí)現(xiàn)兩套API。
因此,對于ReentrantLock,資源是否可以訪問,則可定義為,只要AQS的state狀態(tài)不為0,并且持有線程不為當(dāng)前線程,則代表資源不可訪問。
而對于CountDownLatch等,資源是否可以訪問,則定義為,只要AQS的狀態(tài)不為0,則代表資源不可訪問。

上文中的關(guān)于資源如果無法獲得,將如何處理,這個(gè)答案很顯然,排隊(duì),再隊(duì)列中等待。如果不想等了,想取消,那么AQS也定義了很多關(guān)于取消的API。這將再后續(xù)方法中描述。

5.AQS方法API

AQS其目的是為了實(shí)現(xiàn)一個(gè)Lock,那么與要實(shí)現(xiàn)的Lock對應(yīng)的話,需要實(shí)現(xiàn)Lock接口。然后再實(shí)現(xiàn)一個(gè)所謂的鎖。這可以參考前面的注釋代碼。ReentrentLock也同理。AQS還分為共享和獨(dú)占兩種實(shí)現(xiàn),那么與Lock對照如下:


AQS方法

實(shí)際上這個(gè)實(shí)現(xiàn)關(guān)心也可以用如下表來說明:

鎖方法 AQS實(shí)現(xiàn) 說明
lock() acquire(1)/acquireShared(1) 獲得鎖,通過獨(dú)占或者共享方法都能實(shí)現(xiàn),傳入的參數(shù)是1,這個(gè)鎖不允許中斷,如果調(diào)用中斷方法將會無響應(yīng)。
lockInterruptibly() accquireInterruptibly(1)/acquireSharedInterruptibly(1) 獲得可以中斷的鎖,支持獨(dú)占和共享兩種方式。
tryLock() tryAcquire(1)/tryAcquireShared(1) 嘗試獲得鎖,獨(dú)占和共享都可以實(shí)現(xiàn),但是不支持超時(shí),會無限等待。
tryLock(timeout) tryAcquireNanos(1,nanos)/tryAcquireSharedNanos(1,nanos) 支持超時(shí)時(shí)間的tryLock方法,當(dāng)超時(shí)時(shí)間達(dá)到之后,不再等待。
unlock() release(1)/releaseShared(1) 釋放鎖,可以通過共享或者獨(dú)占的方式調(diào)用
unlock() tryRelease(1)/tryReleaseShared(1) unlock的時(shí)候,需要調(diào)用tryRelease嘗試釋放鎖。
newCondition() newCondition() 這個(gè)方法將new一個(gè)條件變量ConditionObject,之后通過Condition產(chǎn)生的等待線程都將進(jìn)入這個(gè)等待隊(duì)列
hasQueuedThreads() hasQueuedThreads() 判斷該隊(duì)列中是否存在等待的線程,通常用head和tail對比是否相等來返回true和false。

下面再看看這些常用方法是如何實(shí)現(xiàn)的。

5.1 acquire

acquire采用獨(dú)占模式來實(shí)現(xiàn),不可中斷,然后通過至少調(diào)用一次tryAcquire來實(shí)現(xiàn)。如果一次不能成功,線程將排隊(duì),可能反復(fù)被阻塞與取消阻塞,循環(huán)調(diào)用tryAcquire直到成功。這個(gè)方法可以用于被子類實(shí)現(xiàn)。

public final void acquire(int arg) {
    //如果調(diào)用tryAcquire方法不成功且acquireQueued入隊(duì)不成功,則調(diào)用自我中斷。
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

這個(gè)方法的邏輯很明顯,調(diào)用tryAcquire,如果成功則獲得鎖,如果成功,則調(diào)用acquireQueued入隊(duì),再不成功則自我中斷。
tryAcquire 這個(gè)方法需要子類單獨(dú)實(shí)現(xiàn)。

5.2 acquireQueued

此方法,主要是通過循環(huán),多次嘗試獲得鎖。調(diào)用tryAcquire方法。反之則調(diào)用park方法進(jìn)行等待。如果這個(gè)過長產(chǎn)生異常,則調(diào)用cancelAcquire方法進(jìn)行取消。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

5.3 acquireShared

如果采用共享式的獲取鎖的方法:
則將會調(diào)用tryAcquireShared方法,如果不成功則調(diào)用doAcquireShared。

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

5.4 doAcquireShared

代碼如下:

private void doAcquireShared(int arg) {
    //添加一個(gè)共享節(jié)點(diǎn)
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //拿到p的前一個(gè)節(jié)點(diǎn)
            final Node p = node.predecessor();
            //如果p不為head
            if (p == head) {
                //則說明p沒有獲得鎖,則p繼續(xù)調(diào)用try方法
                int r = tryAcquireShared(arg);
                //如果try方法返回結(jié)果大于0,則說明互獲得鎖 
                if (r >= 0) {
                    //將當(dāng)前節(jié)點(diǎn)移除
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    //如果出現(xiàn)異常,則自我中斷
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //如果p為head節(jié)點(diǎn),則調(diào)用shouldParkAfterFailedAcquire對節(jié)點(diǎn)狀態(tài)和指針進(jìn)行check。如果通過,則將線程park,之后等待被喚醒,被喚醒之后調(diào)用中斷方法。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //如果失敗,則調(diào)用取消入隊(duì)方法
        if (failed)
            cancelAcquire(node);
    }
}

可以看出共享和獨(dú)占模式首先Node的狀態(tài)不同。

5.5 acquireInterruptibly

實(shí)現(xiàn)一個(gè)可中斷的lock方法。
代碼如下:

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
        //判斷中斷狀態(tài)
    if (Thread.interrupted())
        throw new InterruptedException();
        //調(diào)用tryAcquire方法
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

實(shí)際上可中斷狀態(tài)的lock方法,只是在一開始就判斷了Thread的interrupt狀態(tài)。之后如果tryAcquire不成功的話,調(diào)用doAcquireInterruptibly。這個(gè)方法又是一個(gè)循環(huán)調(diào)用tryAcquire的方法。

5.6 doAcquireInterruptibly

調(diào)用doAcquireInterruptibly方法。

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    //創(chuàng)建一個(gè)獨(dú)占類型的節(jié)點(diǎn)。
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        //死循環(huán)
        for (;;) {
            //拿到節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)
            final Node p = node.predecessor();
            //如果為head且調(diào)用tryAcquire方法成功,則將當(dāng)前節(jié)點(diǎn)設(shè)置為head,將前一個(gè)節(jié)點(diǎn)移除。
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            //執(zhí)行shouldParkAfterFailedAcquire檢查。
            if (shouldParkAfterFailedAcquire(p, node) &&
               //執(zhí)行park并檢查中斷
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
       //如果狀態(tài)為false,則取消
        if (failed)
            cancelAcquire(node);
    }
}

5.7 acquireSharedInterruptibly

此方法與acquireInterruptibly類似

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

首先判斷中斷狀態(tài),之后再調(diào)用doAcquireSharedInterruptibly。

5.8 doAcquireSharedInterruptibly

這是共享模式獲取鎖的主要執(zhí)行方法,與獨(dú)占模式類似。

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //定義一個(gè)共享模式的節(jié)點(diǎn)。
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
       //死循環(huán)
        for (;;) {
            //獲取前節(jié)點(diǎn)
            final Node p = node.predecessor();
            //如果前節(jié)點(diǎn)為head,則調(diào)用tryAcquireShared
            if (p == head) {
                int r = tryAcquireShared(arg);
                //如果tryAcquireShared結(jié)果大于0則說明獲取鎖成功,將前節(jié)點(diǎn)清除
                if (r >= 0) {
                    //設(shè)置當(dāng)前節(jié)點(diǎn)為head,且調(diào)用release方法
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //調(diào)用檢查方法
            if (shouldParkAfterFailedAcquire(p, node) &&
                //執(zhí)行park和中斷 
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
       //如果失敗則取消入隊(duì)
        if (failed)
            cancelAcquire(node);
    }
}

5.9 doAcquireNanos

這個(gè)方法是實(shí)現(xiàn)tryAcquireNanos的關(guān)鍵方法。

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    //判斷超時(shí)時(shí)間的有效性
    if (nanosTimeout <= 0L)
        return false;
    //根據(jù)超時(shí)時(shí)間計(jì)算deadline
    final long deadline = System.nanoTime() + nanosTimeout;
    //添加Node
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
       //死循環(huán)
        for (;;) {
            //判斷上一個(gè)節(jié)點(diǎn)是否為head,且是否獲得鎖
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            //判斷是否已經(jīng)超時(shí)
            nanosTimeout = deadline - System.nanoTime();
            //如果超時(shí)則返回false 退出
            if (nanosTimeout <= 0L)
                return false;
            //反之則調(diào)用參數(shù)檢查
            if (shouldParkAfterFailedAcquire(p, node) &&
                //如果此使超時(shí)時(shí)間大于1000納秒
                nanosTimeout > spinForTimeoutThreshold)
                //調(diào)用帶超時(shí)參數(shù)的park方法
                LockSupport.parkNanos(this, nanosTimeout);
            //如果中斷則拋出中斷異常
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
       //如果失敗則取消入隊(duì)
        if (failed)
            cancelAcquire(node);
    }
}

5.10 doAcquireSharedNanos

對u有doAcquireSharedNanos方法,實(shí)際上與doAcquireNanos類似。

private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    //判斷超時(shí)時(shí)間
    if (nanosTimeout <= 0L)
        return false;
    //計(jì)算deadline
    final long deadline = System.nanoTime() + nanosTimeout;
    //此處添加共享模式的節(jié)點(diǎn)
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
       //死循環(huán)
        for (;;) {
            //獲得前節(jié)點(diǎn)
            final Node p = node.predecessor();
            //判斷前節(jié)點(diǎn)是否為head
            if (p == head) {
                //用共享模式再次獲取鎖
                int r = tryAcquireShared(arg);
                //如果成功
                if (r >= 0) {
                   //設(shè)置head狀態(tài)并喚醒
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                //執(zhí)行帶超時(shí)的park方法
                LockSupport.parkNanos(this, nanosTimeout);
            //線程中斷
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        //如果失敗則取消
        if (failed)
            cancelAcquire(node);
    }
}

5.11 release

public final boolean release(int arg) {
    //調(diào)用tryRelease方法
    if (tryRelease(arg)) {
        //定義head
        Node h = head;
        //如果head不為空,且等待狀態(tài)不為0
        if (h != null && h.waitStatus != 0)
            //將后續(xù)的節(jié)點(diǎn)都執(zhí)行unpark
            unparkSuccessor(h);
        //之后返回true
        return true;
    }
    //如果釋放不成功則返回false
    return false;
}

與acquire方法類似,需要子類實(shí)現(xiàn)tryRelease方法。

5.12 releaseShared

public final boolean releaseShared(int arg) {
   //調(diào)用try方法
    if (tryReleaseShared(arg)) {
       //調(diào)用do方法
        doReleaseShared();
        return true;
    }
    return false;
}

5.13 unparkSuccessor

這個(gè)方法將獨(dú)占的后續(xù)Node全部喚醒。

private void unparkSuccessor(Node node) {
   //判斷狀態(tài)如果為負(fù)數(shù),則清理,將其改為0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //s為后續(xù)節(jié)點(diǎn)
    Node s = node.next;
    //如果s不為空,且s狀態(tài)大于0
    if (s == null || s.waitStatus > 0) {
        s = null;
        //循環(huán)遍歷
        for (Node t = tail; t != null && t != node; t = t.prev)
            //如果t的狀態(tài)小于0 將s賦值為t 
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //將s對應(yīng)的線程喚醒
        LockSupport.unpark(s.thread);
}

5.14 doReleaseShared

共享方法的釋放鎖過程,與獨(dú)占模式不同的是,獨(dú)占狀態(tài),只需要判斷當(dāng)前節(jié)點(diǎn)是否為空,之后修改當(dāng)前節(jié)點(diǎn)的狀態(tài),并執(zhí)行unpark。而共享模式,則除了判斷是否為空之外,還需要判斷狀態(tài)的具體情況。

 private void doReleaseShared() {
//死循環(huán)
for (;;) {
    Node h = head;
    //如果h不為空
    if (h != null && h != tail) {
        int ws = h.waitStatus;
        //獲得h的狀態(tài)為ws,如果為SIGNAL
        if (ws == Node.SIGNAL) {
            //將其改為0 如果修改失敗則ccontinue 下次循環(huán)繼續(xù)修改
            if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                continue;            // loop to recheck cases
            //將后續(xù)的node調(diào)用unpark
            unparkSuccessor(h);
        }
        //反之 如果ws狀態(tài)不為SIGNAL 則 將其從0改為PROPAGATE狀態(tài) 如果失敗則continue
        else if (ws == 0 &&
                 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
            continue;                // loop on failed CAS
    }
    //如果h為head 則直接break結(jié)束循環(huán)
    if (h == head)                   // loop if head changed
        break;
}
}

6.總結(jié)

AQS是一個(gè)模板方法類,用以實(shí)現(xiàn)ReentrantLock和CountDownLatch等并發(fā)工具。需要注意的是,state是標(biāo)識資源被占用的狀態(tài),如果為0,則說明沒有鎖定。之后如果獲得鎖,則state加1,如果釋放鎖,則state減1,但是這個(gè)過程不是在AQS中實(shí)現(xiàn)的,tryAcquire,以及tryAcquireShared等方法,這需要具體的實(shí)現(xiàn)類來實(shí)現(xiàn)。
AQS本身的數(shù)據(jù)結(jié)構(gòu)是一個(gè)以Node組成的鏈表,ConditionObject是AQS用以支持條件變量的實(shí)現(xiàn),其本身也是一個(gè)單向鏈表組成的隊(duì)列。
后面將在ReentrantLock等并發(fā)工具類中具體來說明AQS的實(shí)現(xiàn)細(xì)節(jié)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評論 2 380