深入淺出ReentrantLock

簡(jiǎn)書(shū) 占小狼
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處狐援,謝謝钢坦!

前言

java5之后,并發(fā)包中新增了Lock接口(以及相關(guān)實(shí)現(xiàn)類(lèi))用來(lái)實(shí)現(xiàn)鎖的功能啥酱,它提供了與synchronized關(guān)鍵字類(lèi)似的同步功能爹凹。既然有了synchronized這種內(nèi)置的鎖功能,為何要新增Lock接口镶殷?先來(lái)想象一個(gè)場(chǎng)景:手把手的進(jìn)行鎖獲取和釋放禾酱,先獲得鎖A,然后再獲取鎖B,當(dāng)獲取鎖B后釋放鎖A同時(shí)獲取鎖C宇植,當(dāng)鎖C獲取后,再釋放鎖B同時(shí)獲取鎖D埋心,以此類(lèi)推指郁,這種場(chǎng)景下,synchronized關(guān)鍵字就不那么容易實(shí)現(xiàn)了拷呆,而使用Lock卻顯得容易許多闲坎。

synchronized不了解的可以看看這篇干貨 深入淺出synchronized

定義

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        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;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

    }
    //默認(rèn)非公平鎖
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    //fair為false時(shí),采用公平鎖策略
    public ReentrantLock(boolean fair) {    
        sync = fair ? new FairSync() : new NonfairSync();
    }
    public void lock() {
        sync.lock();
    }
    public void unlock() {    sync.release(1);}
    public Condition newCondition() {    
        return sync.newCondition();
    }
    ...
}

從源代碼可以Doug lea巧妙的采用組合模式把lock和unlock方法委托給同步器完成茬斧。

使用方式

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
  while(條件判斷表達(dá)式) {
      condition.wait();
  }
 // 處理邏輯
} finally {
    lock.unlock();
}

需要顯示的獲取鎖腰懂,并在finally塊中顯示的釋放鎖,目的是保證在獲取到鎖之后项秉,最終能夠被釋放绣溜。

在深入理解ReentrantLock的實(shí)現(xiàn)原理之前,我們先了解一下java同步器娄蔼。深入淺出java同步器

非公平鎖實(shí)現(xiàn)

在非公平鎖中怖喻,每當(dāng)線(xiàn)程執(zhí)行l(wèi)ock方法時(shí),都嘗試?yán)肅AS把state從0設(shè)置為1岁诉。

那么Doug lea是如何實(shí)現(xiàn)鎖的非公平性呢锚沸?
我們假設(shè)這樣一個(gè)場(chǎng)景:

  1. 持有鎖的線(xiàn)程A正在running,隊(duì)列中有線(xiàn)程BCDEF被掛起并等待被喚醒涕癣;
  2. 在某一個(gè)時(shí)間點(diǎn)塑煎,線(xiàn)程A執(zhí)行unlock瓦灶,喚醒線(xiàn)程B;
  3. 同時(shí)線(xiàn)程G執(zhí)行l(wèi)ock,這個(gè)時(shí)候會(huì)發(fā)生什么磕谅?線(xiàn)程B和G擁有相同的優(yōu)先級(jí),這里講的優(yōu)先級(jí)是指獲取鎖的優(yōu)先級(jí)南蓬,同時(shí)執(zhí)行CAS指令競(jìng)爭(zhēng)鎖芭商。如果恰好線(xiàn)程G成功了,線(xiàn)程B就得重新掛起等待被喚醒须蜗。

通過(guò)上述場(chǎng)景描述硅确,我們可以看書(shū),即使線(xiàn)程B等了很長(zhǎng)時(shí)間也得和新來(lái)的線(xiàn)程G同時(shí)競(jìng)爭(zhēng)鎖明肮,如此的不公平菱农。

static final class NonfairSync extends Sync {
    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    public final void acquire(int arg) {    
        if (!tryAcquire(arg) && 
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))       
          selfInterrupt();
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

下面我們用線(xiàn)程A和線(xiàn)程B來(lái)描述非公平鎖的競(jìng)爭(zhēng)過(guò)程。

  1. 線(xiàn)程A和B同時(shí)執(zhí)行CAS指令柿估,假設(shè)線(xiàn)程A成功循未,線(xiàn)程B失敗,則表明線(xiàn)程A成功獲取鎖,并把同步器中的exclusiveOwnerThread設(shè)置為線(xiàn)程A的妖。
  2. 競(jìng)爭(zhēng)失敗的線(xiàn)程B绣檬,在nonfairTryAcquire方法中,會(huì)再次嘗試獲取鎖嫂粟,Doug lea會(huì)在多處嘗試重新獲取鎖娇未,應(yīng)該是在這段時(shí)間如果線(xiàn)程A釋放鎖,線(xiàn)程B就可以直接獲取鎖而不用掛起星虹。完整的執(zhí)行流程如下:

同步器那塊的邏輯在深入淺出java同步器一文中已經(jīng)講解的很清楚零抬。

公平鎖實(shí)現(xiàn)

在公平鎖中,每當(dāng)線(xiàn)程執(zhí)行l(wèi)ock方法時(shí)宽涌,如果同步器的隊(duì)列中有線(xiàn)程在等待平夜,則直接加入到隊(duì)列中。
場(chǎng)景分析:

  1. 持有鎖的線(xiàn)程A正在running卸亮,對(duì)列中有線(xiàn)程BCDEF被掛起并等待被喚醒忽妒;
  2. 線(xiàn)程G執(zhí)行l(wèi)ock,隊(duì)列中有線(xiàn)程BCDEF在等待兼贸,線(xiàn)程G直接加入到隊(duì)列的對(duì)尾锰扶。

所以每個(gè)線(xiàn)程獲取鎖的過(guò)程是公平的,等待時(shí)間最長(zhǎng)的會(huì)最先被喚醒獲取鎖寝受。

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        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;
    }
}

重入鎖實(shí)現(xiàn)

重入鎖坷牛,即線(xiàn)程可以重復(fù)獲取已經(jīng)持有的鎖。在非公平和公平鎖中很澄,都對(duì)重入鎖進(jìn)行了實(shí)現(xiàn)京闰。

    if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }

條件變量Condition

條件變量很大一個(gè)程度上是為了解決Object.wait/notify/notifyAll難以使用的問(wèn)題。

public class ConditionObject implements Condition, java.io.Serializable {
    /** First node of condition queue. */
    private transient Node firstWaiter;
    /** Last node of condition queue. */
    private transient Node lastWaiter;
    public final void signal() {}
    public final void signalAll() {}
    public final void awaitUninterruptibly() {}  
    public final void await() throws InterruptedException {}
}
  1. Synchronized中甩苛,所有的線(xiàn)程都在同一個(gè)object的條件隊(duì)列上等待蹂楣。而ReentrantLock中,每個(gè)condition都維護(hù)了一個(gè)條件隊(duì)列讯蒲。
  2. 每一個(gè)Lock可以有任意數(shù)據(jù)的Condition對(duì)象痊土,Condition是與Lock綁定的,所以就有Lock的公平性特性:如果是公平鎖墨林,線(xiàn)程為按照FIFO的順序從Condition.await中釋放赁酝,如果是非公平鎖,那么后續(xù)的鎖競(jìng)爭(zhēng)就不保證FIFO順序了旭等。
  3. Condition接口定義的方法酌呆,await對(duì)應(yīng)于Object.waitsignal對(duì)應(yīng)于Object.notify搔耕,signalAll對(duì)應(yīng)于Object.notifyAll隙袁。特別說(shuō)明的是Condition的接口改變名稱(chēng)就是為了避免與Object中的wait/notify/notifyAll的語(yǔ)義和使用上混淆。

先看一個(gè)condition在生產(chǎn)者消費(fèi)者的應(yīng)用場(chǎng)景:

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by j_zhan on 2016/7/13.
 */
public class Queue<T> {
    private final T[] items;
    private final Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();
    private int head, tail, count;
    public Queue(int maxSize) {
        items = (T[]) new Object[maxSize];
    }
    public Queue() {
        this(10);
    }

    public void put(T t) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                //數(shù)組滿(mǎn)時(shí),線(xiàn)程進(jìn)入等待隊(duì)列掛起菩收。線(xiàn)程被喚醒時(shí)梨睁,從這里返回。
                notFull.await(); 
            }
            items[tail] = t;
            if (++tail == items.length) {
                tail = 0;
            }
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();
            }
            T o = items[head];
            items[head] = null;//GC
            if (++head == items.length) {
                head = 0;
            }
            --count;
            notFull.signal();
            return o;
        } finally {
            lock.unlock();
        }
    }
}

假設(shè)線(xiàn)程AB在并發(fā)的往items中插入數(shù)據(jù)娜饵,當(dāng)items中元素存滿(mǎn)時(shí)而姐。如果線(xiàn)程A獲取到鎖,繼續(xù)添加數(shù)據(jù)划咐,滿(mǎn)足count == items.length條件,導(dǎo)致線(xiàn)程A執(zhí)行await方法钧萍。
ReentrantLock是獨(dú)占鎖褐缠,同一時(shí)刻只有一個(gè)線(xiàn)程能獲取到鎖,所以在lock.lock()和lock.unlock()之間可能有一次釋放鎖的操作(同樣也必然還有一次獲取鎖的操作)风瘦。在Quene類(lèi)中队魏,不管take還是put,在線(xiàn)程持有鎖之后只有await()方法有可能釋放鎖万搔,然后掛起線(xiàn)程胡桨,一旦條件滿(mǎn)足就被喚醒,再次獲取鎖瞬雹。具體實(shí)現(xiàn)如下:

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    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);
}

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

await實(shí)現(xiàn)邏輯:

  1. 將線(xiàn)程A加入到條件等待隊(duì)列中昧谊,如果最后一個(gè)節(jié)點(diǎn)是取消狀態(tài),則從對(duì)列中刪除酗捌。
  2. 線(xiàn)程A釋放鎖呢诬,實(shí)質(zhì)上是線(xiàn)程A修改AQS的狀態(tài)state為0,并喚醒AQS等待隊(duì)列中的線(xiàn)程B胖缤,線(xiàn)程B被喚醒后尚镰,嘗試獲取鎖,接下去的過(guò)程就不重復(fù)說(shuō)明了哪廓。
  3. 線(xiàn)程A釋放鎖并喚醒線(xiàn)程B之后狗唉,如果線(xiàn)程A不在AQS的同步隊(duì)列中,線(xiàn)程A將通過(guò)LockSupport.park進(jìn)行掛起操作涡真。
  4. 隨后分俯,線(xiàn)程A等待被喚醒,當(dāng)線(xiàn)程A被喚醒時(shí)哆料,會(huì)通過(guò)acquireQueued方法競(jìng)爭(zhēng)鎖澳迫,如果失敗,繼續(xù)掛起剧劝。如果成功橄登,線(xiàn)程A從await位置恢復(fù)。

假設(shè)線(xiàn)程B獲取鎖之后,執(zhí)行了take操作和條件變量的signal拢锹,signal通過(guò)某種實(shí)現(xiàn)喚醒了線(xiàn)程A谣妻,具體實(shí)現(xiàn)如下:

 public final void signal() {
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     Node first = firstWaiter;
     if (first != null)
         doSignal(first);
 }

 private void doSignal(Node first) {
     do {
         if ((firstWaiter = first.nextWaiter) == null)
             lastWaiter = null;
         first.nextWaiter = null;
     } while (!transferForSignal(first) &&
              (first = firstWaiter) != null);
 }

 final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    Node p = enq(node); //線(xiàn)程A插入到AQS的等待隊(duì)列中
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

signal實(shí)現(xiàn)邏輯:

  1. 接著上述場(chǎng)景,線(xiàn)程B執(zhí)行了signal方法卒稳,取出條件隊(duì)列中的第一個(gè)非CANCELLED節(jié)點(diǎn)線(xiàn)程蹋半,即線(xiàn)程A。另外充坑,signalAll就是喚醒條件隊(duì)列中所有非CANCELLED節(jié)點(diǎn)線(xiàn)程减江。遇到CANCELLED線(xiàn)程就需要將其從隊(duì)列中刪除。
  2. 通過(guò)CAS修改線(xiàn)程A的waitStatus為0捻爷,表示該節(jié)點(diǎn)已經(jīng)不是等待條件狀態(tài)辈灼,并將線(xiàn)程A插入到AQS的等待隊(duì)列中。
  3. 喚醒線(xiàn)程A也榄,線(xiàn)程A和別的線(xiàn)程進(jìn)行鎖的競(jìng)爭(zhēng)巡莹。

總結(jié)

  1. ReentrantLock提供了內(nèi)置鎖類(lèi)似的功能和內(nèi)存語(yǔ)義。
  2. 此外甜紫,ReetrantLock還提供了其它功能降宅,包括定時(shí)的鎖等待、可中斷的鎖等待囚霸、公平性腰根、以及實(shí)現(xiàn)非塊結(jié)構(gòu)的加鎖、Condition拓型,對(duì)線(xiàn)程的等待和喚醒等操作更加靈活唠雕,一個(gè)ReentrantLock可以有多個(gè)Condition實(shí)例,所以更有擴(kuò)展性吨述,不過(guò)ReetrantLock需要顯示的獲取鎖岩睁,并在finally中釋放鎖,否則后果很?chē)?yán)重揣云。
  3. ReentrantLock在性能上似乎優(yōu)于Synchronized捕儒,其中在jdk1.6中略有勝出,在1.5中是遠(yuǎn)遠(yuǎn)勝出邓夕。那么為什么不放棄內(nèi)置鎖刘莹,并在新代碼中都使用ReetrantLock?
  4. 在java1.5中焚刚, 內(nèi)置鎖與ReentrantLock相比有例外一個(gè)優(yōu)點(diǎn):在線(xiàn)程轉(zhuǎn)儲(chǔ)中能給出在哪些調(diào)用幀中獲得了哪些鎖点弯,并能夠檢測(cè)和識(shí)別發(fā)生死鎖的線(xiàn)程。Reentrant的非塊狀特性任然意味著矿咕,獲取鎖的操作不能與特定的棧幀關(guān)聯(lián)起來(lái)抢肛,而內(nèi)置鎖卻可以狼钮。
  5. 因?yàn)閮?nèi)置鎖時(shí)JVM的內(nèi)置屬性,所以未來(lái)更可能提升synchronized而不是ReentrantLock的性能捡絮。例如對(duì)線(xiàn)程封閉的鎖對(duì)象消除優(yōu)化熬芜,通過(guò)增加鎖粒度來(lái)消除內(nèi)置鎖的同步。

END福稳。
我是占小狼涎拉。
在魔都艱苦奮斗,白天是上班族的圆,晚上是知識(shí)服務(wù)工作者鼓拧。
讀完我的文章有收獲,記得關(guān)注和點(diǎn)贊哦越妈,如果非要打賞季俩,我也是不會(huì)拒絕的啦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叮称,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藐鹤,更是在濱河造成了極大的恐慌瓤檐,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娱节,死亡現(xiàn)場(chǎng)離奇詭異挠蛉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)肄满,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)谴古,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人稠歉,你說(shuō)我怎么就攤上這事掰担。” “怎么了怒炸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵带饱,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我阅羹,道長(zhǎng)勺疼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任捏鱼,我火速辦了婚禮执庐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘导梆。我一直安慰自己轨淌,他們只是感情好迂烁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著猿诸,像睡著了一般婚被。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梳虽,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天址芯,我揣著相機(jī)與錄音,去河邊找鬼窜觉。 笑死谷炸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的禀挫。 我是一名探鬼主播旬陡,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼语婴!你這毒婦竟也來(lái)了描孟?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤砰左,失蹤者是張志新(化名)和其女友劉穎匿醒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缠导,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廉羔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了僻造。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憋他。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖髓削,靈堂內(nèi)的尸體忽然破棺而出竹挡,到底是詐尸還是另有隱情,我是刑警寧澤立膛,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布此迅,位于F島的核電站,受9級(jí)特大地震影響旧巾,放射性物質(zhì)發(fā)生泄漏耸序。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一鲁猩、第九天 我趴在偏房一處隱蔽的房頂上張望坎怪。 院中可真熱鬧,春花似錦廓握、人聲如沸搅窿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)男应。三九已至闹司,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沐飘,已是汗流浹背游桩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耐朴,地道東北人借卧。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像筛峭,于是被迫代替她去往敵國(guó)和親铐刘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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