JUC源碼分析-JUC鎖(五):Phaser

概述

Phaser是JDK 7新增的一個同步輔助類,在功能上跟CyclicBarrier和CountDownLatch差不多,但支持更豐富的用法。與JUC中多數同步類不同,它并不是通過AQS來實現的,而是用了另外一種同步機制。本文我們將從多個方面分析Phaser這個同步類。

Phaser運行示意圖:

Phaser運行機制

在開始Phaser的深入分析之前,我們先大概看一下對于Phaser的一些概念介紹(由于涉及Phaser的部分方法,可結合源碼分析學習本節內容):

  1. Registration(注冊):
    跟其他barrier不同,在phaser上注冊的parties會隨著時間的變化而變化。任務可以隨時注冊(使用方法register,bulkRegister注冊,或者由構造器確定初始parties),并且在任何抵達點可以隨意地撤銷注冊(方法arriveAndDeregister)。就像大多數基本的同步結構一樣,注冊和撤銷只影響內部count;不會創建更深的內部記錄,所以任務不能查詢他們是否已經注冊。(不過,可以通過繼承來實現類似的記錄)

  2. Synchronization(同步機制):
    CyclicBarrier一樣,Phaser也可以重復await。方法arriveAndAwaitAdvance的效果類似CyclicBarrier.await。phaser的每一代都有一個相關的phase number,初始值為0,當所有注冊的任務都到達phaser時phase+1,到達最大值(Integer.MAX_VALUE)之后清零。使用phase number可以獨立控制 到達phaser 和 等待其他線程 的動作,通過下面兩種類型的方法:

    • Arrival(到達機制)
      arrivearriveAndDeregister方法記錄到達狀態。這些方法不會阻塞,但是會返回一個相關的arrival phase number;也就是說,phase number用來確定到達狀態。當所有任務都到達給定phase時,可以執行一個可選的函數,這個函數通過重寫onAdvance方法實現,通常可以用來控制終止狀態。重寫此方法類似于為CyclicBarrier提供一個barrierAction,但比它更靈活。
    • Waiting(等待機制)
      awaitAdvance方法需要一個表示arrival phase number的參數,并且在phaser前進到與給定phase不同的phase時返回。和CyclicBarrier不同,即使等待線程已經被中斷,awaitAdvance方法也會一直等待。中斷狀態和超時時間同樣可用,但是當任務等待中斷或超時后未改變phaser的狀態時會遭遇異常。如果有必要,在方法forceTermination之后可以執行這些異常的相關的handler進行恢復操作,Phaser也可能被ForkJoinPool中的任務使用,這樣在其他任務阻塞等待一個phase時可以保證足夠的并行度來執行任務。
  3. Termination(終止機制):
    可以用isTerminated方法檢查Phaser的終止狀態。在終止時,所有同步方法立刻返回一個負值。在終止時嘗試注冊也沒有效果。當調用onAdvance返回true時Termination被觸發。當deregistration操作使已注冊的parties變為0時,onAdvance的默認實現就會返回true。也可以重寫onAdvance方法來定義終止動作。forceTermination方法也可以釋放等待線程并且允許它們終止。

  4. Tiering(分層結構):
    Phaser支持分層結構(樹狀構造)來減少競爭。注冊了大量parties的Phaser可能會因為同步競爭消耗很高的成本, 因此可以設置一些子Phaser來共享一個通用的parent。這樣的話即使每個操作消耗了更多的開銷,但是會提高整體吞吐量。
    在一個分層結構的phaser里,子節點phaser的注冊和取消注冊都通過父節點管理。子節點phaser通過構造或方法register、bulkRegister進行首次注冊時,在其父節點上注冊。子節點phaser通過調用arriveAndDeregister進行最后一次取消注冊時,也在其父節點上取消注冊。

  5. Monitoring(狀態監控):
    由于同步方法可能只被已注冊的parties調用,所以phaser的當前狀態也可能被任何調用者監控。在任何時候,可以通過getRegisteredParties獲取parties數,其中getArrivedParties方法返回已經到達當前phase的parties數。當剩余的parties(通過方法getUnarrivedParties獲取)到達時,phase進入下一代。這些方法返回的值可能只表示短暫的狀態(同步類的通病),所以一般來說在同步結構里并沒有啥卵用。

1. 核心參數和函數列表

private volatile long state;
/**
 * The parent of this phaser, or null if none
 */
private final Phaser parent;
/**
 * The root of phaser tree. Equals this if not in a tree.
 */
private final Phaser root;
//等待線程的棧頂元素,根據phase取模定義為一個奇數header和一個偶數header
private final AtomicReference<QNode> evenQ;
private final AtomicReference<QNode> oddQ;

state狀態說明:
Phaser使用一個long型state值來標識內部狀態:

  • 低0-15位表示未到達parties數;
  • 中16-31位表示等待的parties數;
  • 中32-62位表示phase當前代;
  • 高63位表示當前phaser的終止狀態。

注意:子Phaser的phase在沒有被真正使用之前,允許滯后于它的root節點。這里在后面源碼分析的reconcileState方法里會講解。
Qnode是Phaser定義的內部等待隊列,用于在阻塞時記錄等待線程及相關信息。實現了ForkJoinPool的一個內部接口ManagedBlocker,上面已經說過,Phaser也可能被ForkJoinPool中的任務使用,這樣在其他任務阻塞等待一個phase時可以保證足夠的并行度來執行任務(通過內部實現方法isReleasableblock)。

函數列表:

//構造方法
public Phaser() {
    this(null, 0);
}
public Phaser(int parties) {
    this(null, parties);
}
public Phaser(Phaser parent) {
    this(parent, 0);
}
public Phaser(Phaser parent, int parties)
//注冊一個新的party
public int register()
//批量注冊
public int bulkRegister(int parties)
//使當前線程到達phaser,不等待其他任務到達。返回arrival phase number
public int arrive() 
//使當前線程到達phaser并撤銷注冊,返回arrival phase number
public int arriveAndDeregister()
/*
 * 使當前線程到達phaser并等待其他任務到達,等價于awaitAdvance(arrive())。
 * 如果需要等待中斷或超時,可以使用awaitAdvance方法完成一個類似的構造。
 * 如果需要在到達后取消注冊,可以使用awaitAdvance(arriveAndDeregister())。
 */
public int arriveAndAwaitAdvance()
//等待給定phase數,返回下一個 arrival phase number
public int awaitAdvance(int phase)
//阻塞等待,直到phase前進到下一代,返回下一代的phase number
public int awaitAdvance(int phase) 
//響應中斷版awaitAdvance
public int awaitAdvanceInterruptibly(int phase) throws InterruptedException
public int awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit)
    throws InterruptedException, TimeoutException
//使當前phaser進入終止狀態,已注冊的parties不受影響,如果是分層結構,則終止所有phaser
public void forceTermination()

2. 使用示例

public class PhaserTest1 {

    private static Phaser ps;
    private static int SIZE = 10;

    public static void main(String[] args) throws InterruptedException {
        ps = new Phaser(SIZE);
        List<Runnable> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(new MyTask());
        }
        //runTasks(list);
        startTasks(list, 3);
    }

    static class MyTask implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " wait for phaser");
            ps.arriveAndAwaitAdvance();
            //ps.awaitAdvance(ps.arrive());

            System.out.println(Thread.currentThread().getName() + " continued");
        }
    }

    //替代CountDownLatch
    static void runTasks(List<Runnable> tasks) {
        final Phaser phaser = new Phaser(1); // "1" to register self
        // create and start threads
        for (final Runnable task : tasks) {
            phaser.register();
            new Thread() {
                public void run() {
                    System.out.println("wait phaser");
                    phaser.arriveAndAwaitAdvance(); // await all creation
                    //phaser.awaitAdvance(2);
                    System.out.println("phaser is done");
                    task.run();
                }
            }.start();
        }

        // allow threads to start and deregister self
        phaser.arriveAndDeregister();
    }

    //使用多階(phase)
    static void startTasks(List<Runnable> tasks, final int iterations) {
        final Phaser phaser = new Phaser() {
            protected boolean onAdvance(int phase, int registeredParties) {
                return phase >= iterations || registeredParties == 0;
            }
        };
        phaser.register();
        for (final Runnable task : tasks) {
            phaser.register();
            new Thread() {
                public void run() {
                    do {
                        task.run();
                        phaser.arriveAndAwaitAdvance();
                    } while (!phaser.isTerminated());
                }
            }.start();
        }
        phaser.arriveAndDeregister(); // deregister self, don't wait
    }
}

3. 源碼分析

3.1 register()

//注冊一個新的party
public int register() {
    return doRegister(1);
}
private int doRegister(int registrations) {
    // adjustment to state
    long adjust = ((long)registrations << PARTIES_SHIFT) | registrations;
    final Phaser parent = this.parent;
    int phase;
    for (;;) {
        long s = (parent == null) ? state : reconcileState();
        int counts = (int)s;
        int parties = counts >>> PARTIES_SHIFT;//獲取已注冊parties數
        int unarrived = counts & UNARRIVED_MASK;//未到達數
        if (registrations > MAX_PARTIES - parties)
            throw new IllegalStateException(badRegister(s));
        phase = (int)(s >>> PHASE_SHIFT);//獲取當前代
        if (phase < 0)
            break;
        if (counts != EMPTY) {                  // not 1st registration
            if (parent == null || reconcileState() == s) {
                if (unarrived == 0)             // wait out advance
                    root.internalAwaitAdvance(phase, null);//等待其他任務到達
                else if (UNSAFE.compareAndSwapLong(this, stateOffset,
                                                   s, s + adjust))//更新注冊的parties數
                    break;
            }
        }
        else if (parent == null) {              // 1st root registration
            long next = ((long)phase << PHASE_SHIFT) | adjust;
            if (UNSAFE.compareAndSwapLong(this, stateOffset, s, next))//更新phase
                break;
        }
        else {
            //分層結構,子phaser首次注冊用父節點管理
            synchronized (this) {               // 1st sub registration
                if (state == s) {               // recheck under lock
                    phase = parent.doRegister(1);//分層結構,使用父節點注冊
                    if (phase < 0)
                        break;
                    // finish registration whenever parent registration
                    // succeeded, even when racing with termination,
                    // since these are part of the same "transaction".
                    //由于在同一個事務里,即使phaser已終止,也會完成注冊
                    while (!UNSAFE.compareAndSwapLong
                           (this, stateOffset, s,
                            ((long)phase << PHASE_SHIFT) | adjust)) {//更新phase
                        s = state;
                        phase = (int)(root.state >>> PHASE_SHIFT);
                        // assert (int)s == EMPTY;
                    }
                    break;
                }
            }
        }
    }
    return phase;
}

說明: register方法為phaser添加一個新的party,如果onAdvance正在運行,那么這個方法會等待它運行結束再返回結果。如果當前phaser有父節點,并且當前phaser上沒有已注冊的party,那么就會交給父節點注冊。
registerbulkRegister都由doRegister實現,大概流程如下:

  1. 如果當前操作不是首次注冊,那么直接在當前phaser上更新注冊parties數
  2. 如果是首次注冊,并且當前phaser沒有父節點,說明是root節點注冊,直接更新phase
  3. 如果當前操作是首次注冊,并且當前phaser由父節點,則注冊操作交由父節點,并更新當前phaser的phase
    上面說過,子Phaser的phase在沒有被真正使用之前,允許滯后于它的root節點。非首次注冊時,如果Phaser有父節點,則調用reconcileState()方法解決root節點的phase延遲傳遞問題, 源碼如下:
private long reconcileState() {
    final Phaser root = this.root;
    long s = state;
    if (root != this) {
        int phase, p;
        // CAS to root phase with current parties, tripping unarrived
        while ((phase = (int)(root.state >>> PHASE_SHIFT)) !=
               (int)(s >>> PHASE_SHIFT) &&
               !UNSAFE.compareAndSwapLong
               (this, stateOffset, s,
                s = (((long)phase << PHASE_SHIFT) |
                     ((phase < 0) ? (s & COUNTS_MASK) :
                      (((p = (int)s >>> PARTIES_SHIFT) == 0) ? EMPTY :
                       ((s & PARTIES_MASK) | p))))))
            s = state;
    }
    return s;
}

當root節點的phase已經advance到下一代,但是子節點phaser還沒有,這種情況下它們必須通過更新未到達parties數 完成它們自己的advance操作(如果parties為0,重置為EMPTY狀態)。
回到register方法的第一步,如果當前未到達數為0,說明上一代phase正在進行到達操作,此時調用internalAwaitAdvance()方法等待其他任務完成到達操作,源碼如下:

//阻塞等待phase到下一代
private int internalAwaitAdvance(int phase, QNode node) {
    // assert root == this;
    releaseWaiters(phase-1);          // ensure old queue clean
    boolean queued = false;           // true when node is enqueued
    int lastUnarrived = 0;            // to increase spins upon change
    int spins = SPINS_PER_ARRIVAL;
    long s;
    int p;
    while ((p = (int)((s = state) >>> PHASE_SHIFT)) == phase) {
        if (node == null) {           // spinning in noninterruptible mode
            int unarrived = (int)s & UNARRIVED_MASK;//未到達數
            if (unarrived != lastUnarrived &&
                (lastUnarrived = unarrived) < NCPU)
                spins += SPINS_PER_ARRIVAL;
            boolean interrupted = Thread.interrupted();
            if (interrupted || --spins < 0) { // need node to record intr
                //使用node記錄中斷狀態
                node = new QNode(this, phase, false, false, 0L);
                node.wasInterrupted = interrupted;
            }
        }
        else if (node.isReleasable()) // done or aborted
            break;
        else if (!queued) {           // push onto queue
            AtomicReference<QNode> head = (phase & 1) == 0 ? evenQ : oddQ;
            QNode q = node.next = head.get();
            if ((q == null || q.phase == phase) &&
                (int)(state >>> PHASE_SHIFT) == phase) // avoid stale enq
                queued = head.compareAndSet(q, node);
        }
        else {
            try {
                ForkJoinPool.managedBlock(node);//阻塞給定node
            } catch (InterruptedException ie) {
                node.wasInterrupted = true;
            }
        }
    }

    if (node != null) {
        if (node.thread != null)
            node.thread = null;       // avoid need for unpark()
        if (node.wasInterrupted && !node.interruptible)
            Thread.currentThread().interrupt();
        if (p == phase && (p = (int)(state >>> PHASE_SHIFT)) == phase)
            return abortWait(phase); // possibly clean up on abort
    }
    releaseWaiters(phase);
    return p;
}

簡單介紹下第二個參數node,如果不為空,則說明等待線程需要追蹤中斷狀態或超時狀態。以doRegister中的調用為例,不考慮線程爭用,internalAwaitAdvance大概流程如下:

  1. 首先調用releaseWaiters喚醒上一代所有等待線程,確保舊隊列中沒有遺留的等待線程。
  2. 循環SPINS_PER_ARRIVAL指定的次數或者當前線程被中斷,創建node記錄等待線程及相關信息。
  3. 繼續循環調用ForkJoinPool.managedBlock運行被阻塞的任務
  4. 繼續循環,阻塞任務運行成功被釋放,跳出循環
  5. 最后喚醒當前phase的線程

3.2 arrive()

//使當前線程到達phaser,不等待其他任務到達。返回arrival phase number
public int arrive() {
    return doArrive(ONE_ARRIVAL);
}

private int doArrive(int adjust) {
    final Phaser root = this.root;
    for (;;) {
        long s = (root == this) ? state : reconcileState();
        int phase = (int)(s >>> PHASE_SHIFT);
        if (phase < 0)
            return phase;
        int counts = (int)s;
        //獲取未到達數
        int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);
        if (unarrived <= 0)
            throw new IllegalStateException(badArrive(s));
        if (UNSAFE.compareAndSwapLong(this, stateOffset, s, s-=adjust)) {//更新state
            if (unarrived == 1) {//當前為最后一個未到達的任務
                long n = s & PARTIES_MASK;  // base of next state
                int nextUnarrived = (int)n >>> PARTIES_SHIFT;
                if (root == this) {
                    if (onAdvance(phase, nextUnarrived))//檢查是否需要終止phaser
                        n |= TERMINATION_BIT;
                    else if (nextUnarrived == 0)
                        n |= EMPTY;
                    else
                        n |= nextUnarrived;
                    int nextPhase = (phase + 1) & MAX_PHASE;
                    n |= (long)nextPhase << PHASE_SHIFT;
                    UNSAFE.compareAndSwapLong(this, stateOffset, s, n);
                    releaseWaiters(phase);//釋放等待phase的線程
                }
                //分層結構,使用父節點管理arrive
                else if (nextUnarrived == 0) { //propagate deregistration
                    phase = parent.doArrive(ONE_DEREGISTER);
                    UNSAFE.compareAndSwapLong(this, stateOffset,
                                              s, s | EMPTY);
                }
                else
                    phase = parent.doArrive(ONE_ARRIVAL);
            }
            return phase;
        }
    }
}

說明: arrive方法手動調整到達數,使當前線程到達phaser。arrivearriveAndDeregister都調用了doArrive實現,大概流程如下:

  1. 首先更新state(state - adjust)
  2. 如果當前不是最后一個未到達的任務,直接返回phase
  3. 如果當前是最后一個未到達的任務:
    a) 如果當前是root節點,判斷是否需要終止phaser,CAS更新phase,最后釋放等待的線程;
    b) 如果是分層結構,并且已經沒有下一代未到達的parties,則交由父節點處理doArrive邏輯,然后更新state為EMPTY

3.3 arriveAndAwaitAdvance()

public int arriveAndAwaitAdvance() {
    // Specialization of doArrive+awaitAdvance eliminating some reads/paths
    final Phaser root = this.root;
    for (;;) {
        long s = (root == this) ? state : reconcileState();
        int phase = (int)(s >>> PHASE_SHIFT);
        if (phase < 0)
            return phase;
        int counts = (int)s;
        int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);//獲取未到達數
        if (unarrived <= 0)
            throw new IllegalStateException(badArrive(s));
        if (UNSAFE.compareAndSwapLong(this, stateOffset, s,
                                      s -= ONE_ARRIVAL)) {//更新state
            if (unarrived > 1)
                return root.internalAwaitAdvance(phase, null);//阻塞等待其他任務
            if (root != this)
                return parent.arriveAndAwaitAdvance();//子Phaser交給父節點處理
            long n = s & PARTIES_MASK;  // base of next state
            int nextUnarrived = (int)n >>> PARTIES_SHIFT;
            if (onAdvance(phase, nextUnarrived))//全部到達,檢查是否可銷毀
                n |= TERMINATION_BIT;
            else if (nextUnarrived == 0)
                n |= EMPTY;
            else
                n |= nextUnarrived;
            int nextPhase = (phase + 1) & MAX_PHASE;//計算下一代phase
            n |= (long)nextPhase << PHASE_SHIFT;
            if (!UNSAFE.compareAndSwapLong(this, stateOffset, s, n))//更新state
                return (int)(state >>> PHASE_SHIFT); // terminated
            releaseWaiters(phase);//釋放等待phase的線程
            return nextPhase;
        }
    }
}

說明:使當前線程到達phaser并等待其他任務到達,等價于awaitAdvance(arrive())。如果需要等待中斷或超時,可以使用awaitAdvance方法完成一個類似的構造。如果需要在到達后取消注冊,可以使用awaitAdvance(arriveAndDeregister())。效果類似于CyclicBarrier.await。大概流程如下:

  1. 更新state(state - 1);
  2. 如果未到達數大于1,調用internalAwaitAdvance阻塞等待其他任務到達,返回當前phase
  3. 如果為分層結構,則交由父節點處理arriveAndAwaitAdvance邏輯
  4. 如果未到達數<=1,判斷phaser終止狀態,CAS更新phase到下一代,最后釋放等待當前phase的線程,并返回下一代phase。

3.4 awaitAdvance(int phase)

public int awaitAdvance(int phase) {
    final Phaser root = this.root;
    long s = (root == this) ? state : reconcileState();
    int p = (int)(s >>> PHASE_SHIFT);
    if (phase < 0)
        return phase;
    if (p == phase)
        return root.internalAwaitAdvance(phase, null);
    return p;
}
//響應中斷版awaitAdvance
public int awaitAdvanceInterruptibly(int phase)
    throws InterruptedException {
    final Phaser root = this.root;
    long s = (root == this) ? state : reconcileState();
    int p = (int)(s >>> PHASE_SHIFT);
    if (phase < 0)
        return phase;
    if (p == phase) {
        QNode node = new QNode(this, phase, true, false, 0L);
        p = root.internalAwaitAdvance(phase, node);
        if (node.wasInterrupted)
            throw new InterruptedException();
    }
    return p;
}

說明: awaitAdvance用于阻塞等待線程到達,直到phase前進到下一代,返回下一代的phase number。方法很簡單,不多贅述。awaitAdvanceInterruptibly方法是響應中斷版的awaitAdvance,不同之處在于,調用阻塞時會記錄線程的中斷狀態。

小結

在使用上,Phaser可以實現CyclicBarrier和CountDownLatch類似的功能,而且它支持對任務的動態調整,并支持分層結構來達到更高的吞吐量。

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

推薦閱讀更多精彩內容