JUC锁框架_ ReentrantLock原理分析

上一章详细介绍了AQS的源码,这一章我们来分析JUC框架中最常用的锁ReentrantLock(可重入独占锁,也叫可重入互斥锁)

    public class ReentrantLock implements Lock, java.io.Serializable {}

ReentrantLock实现Lock接口,以及Serializable可序列化接口。

可重入独占锁的意思:

  1. 同一时间只能有一个线程持有这个锁。
  2. 持有这个锁的线程可以再次进入另一个被这个锁锁住的模块,即持有这个锁的线程可以重复获取锁。

一. Lock接口

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;

public interface Lock {

    // 获取锁,如果获取不到,就一直等待。不响应中断请求
    void lock();

    // 获取锁,如果获取不到,就一直等待。如果在线程等待期间有中断请求就抛出异常
    void lockInterruptibly() throws InterruptedException;

    // 尝试用非公平锁方式去获取锁,立即返回。返回true表示获取成功,返回false表示获取失败
    boolean tryLock();

    // 在规定的unit时间内获取锁,如果时间到了还没有获取到锁,则返回false,表示获取失败
    // 如果在线程等待期间有中断请求就抛出异常
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 当前线程释放占用的锁,并唤醒这个锁上的一个等待线程
    void unlock();

    // 创建一个Condition对象
    Condition newCondition();
}

这个接口提供获取锁,释放锁以及生成Condition对象的方法。

二. 构造函数和成员属性

2.1 成员属性

    // ReentrantLock通过sync变量,实现独占锁的操作。
    //Sync是AbstractQueuedSynchronizer的子类
    private final Sync sync;

ReentrantLock只有这一个成员变量,它是AQS的子类,所以可以通过sync实现独占锁的操作。

2.2 构造函数

    // 默认创建的是非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }


    // 根据fair值,决定创建公平锁还是非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

根据参数fair来决定是公平锁还是非公平锁,默认是非公平锁。

三. Sync内部类

这个类也是个抽样类,它的两个子类就是FairSync和NonfairSync。有两个重要方法。

3.1 nonfairTryAcquire方法

使用非公平方式去尝试获取锁

    final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            // 获取锁的记录状态state
            int c = getState();
            // 如果c==0表示当前锁是空闲的
            if (c == 0) {
                // 通过CAS原子操作方式设置锁的状态,如果为true,表示当前线程获取的锁,
                // 为false,锁的状态被其他线程更改,当前线程获取的锁失败
                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;
        }

方法流程:

  1. 调用getState方法获取锁的记录状态c
  2. 如果c==0表示当前锁是空闲的。因为是非公平的方法获取锁,所以直接调用compareAndSetState更改锁的状态,如果成功,表示当前线程获取了锁,如果失败,表示锁的状态别的线程更改了,当前线程获取锁失败。
  3. 如果c不等于0,那么要看当前线程是不是获取锁的线程,因为ReentrantLock是可重入锁,获取锁的线程可以重复获取锁。

3.2 tryRelease方法

尝试释放锁资源,返回true表示完全释放了锁资源,返回false表示还持有锁资源。

因为锁是可重入的,所以锁可能要释放多次。

    protected final boolean tryRelease(int releases) {
            // c表示新的锁的记录状态
            int c = getState() - releases;
            // 如果当前线程不是独占锁的线程,就抛出IllegalMonitorStateException异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            // 标志是否可以释放锁
            boolean free = false;
            // 当新的锁的记录状态为0时,表示可以释放锁
            if (c == 0) {
                free = true;
                // 设置独占锁的线程为null
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

方法流程:

  1. 先计算新的锁的记录状态c。
  2. 如果当前线程不是独占锁的线程,就抛出IllegalMonitorStateException异常。
  3. 当新的锁的记录状态为0时,表示完全释放锁资源,就唤醒另一个等待锁的线程了。
  4. 设置新的锁的记录状态。

四. NonfairSync 非公平锁

    /**
     * 非公平锁
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        // 获取锁,如果没有获取到锁,则当前线程要阻塞等待
        final void lock() {
            // compareAndSetState返回true,表示当前线程获取锁成功。
            // 因为是非公平锁,所以不需要判断AbstractQueuedSynchronizer线程等待队列是否有值
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 调用acquire方法,获取锁
                acquire(1);
        }

        // 尝试获取锁,获取到锁返回true,没有获取到返回false
        protected final boolean tryAcquire(int acquires) {
            // 调用父类的nonfairTryAcquire方法
            return nonfairTryAcquire(acquires);
        }
    }

继承Sync类需要复写两个方法:

  1. lock获取锁的方法。 因为是非公平锁方式获取锁,所以先直接调用compareAndSetState方法,如果返回true,表示锁资源被当前线程持有了。返回false表示锁的状态state不是0,锁资源已经被持有了。则调用acquire方法,再次获取锁,不成功就阻塞当前线程。
  2. tryAcquire方法:尝试获取锁,获取到锁返回true,没有获取到返回false。这里调用父类的nonfairTryAcquire方法

五. FairSync 公平锁

    /**
     * 公平锁
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            // 与非公平锁不同,因为它要考虑线程等待队列是否有值
            acquire(1);
        }

        // 尝试获取锁,与与非公平锁最大的不同就是调用hasQueuedPredecessors()方法
        // hasQueuedPredecessors方法返回true,表示等待线程队列中有一个线程在当前线程之前,
        // 根据公平锁的规则,当前线程不能获取锁。
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            // 获取锁的记录状态
            int c = getState();
            // 如果c==0表示当前锁是空闲的
            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;
        }
    }
  1. lock 方法:与非公平锁相比,它直接调用acquire方法,因为是公平锁,所以必须考虑当前线程是不是CLH队列中第一个(即队列中第一个等待线程)

  2. tryAcquire 方法:与非公平锁相比,会调用调用hasQueuedPredecessors()方法。 hasQueuedPredecessors方法返回true,表示等待线程队列中有一个线程在当前线程之前,根据公平锁的规则,当前线程不能获取锁。

示例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {

    public static void newThread(Lock lock, String name, int time) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程"+Thread.currentThread().getName()+" 开始运行,准备获取锁");
                lock.lock();
                try {
                    System.out.println("====线程"+Thread.currentThread().getName()+" 在run方法中获取了锁");
                    lockAgain();
                    try {
                        Thread.sleep(time);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    System.out.println("----线程"+Thread.currentThread().getName()+" 在run方法中释放了锁");
                    lock.unlock();
                }
            }

            private void lockAgain() {
                lock.lock();
                try {
                    System.out.println("====线程"+Thread.currentThread().getName()+"  在lockAgain方法中再次获取了锁");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    System.out.println("----线程"+Thread.currentThread().getName()+" 在lockAgain方法中释放了锁");
                    lock.unlock();
                }
            }
        },name).start();
    }

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        newThread(lock, "t1111", 1000);
        newThread(lock, "t2222", 1000);
        newThread(lock, "t3333", 1000);
    }
}

从这个例子中可以看出ReentrantLock是一个可重入锁,当前持有锁的线程可以进入另一个被锁住的模块,而且只有都释放完成之后,它才会去唤醒一个等待锁的线程。

注意一个有趣点,虽然我们创建的非公平锁,但是每次释放锁完成后,去唤醒一个等待线程时,你会发现它一定是等待线程中的第一个线程。那么不是和非公平锁定义好像不一样啊。
其实非公平锁和公平锁最大的区别是,当锁是空闲的即没有任何线程持有锁,非公平锁就允许当前线程直接获取锁,而不用考虑是否有其他线程已经在等待这把锁了。而公平锁不是这样,如果有等待线程队列,那么就将当前线程插入到等待线程队列尾。

输出结果是

线程t1111 开始运行,准备获取锁
====线程t1111 在run方法中获取了锁
====线程t1111  在lockAgain方法中再次获取了锁
线程t2222 开始运行,准备获取锁
线程t3333 开始运行,准备获取锁
----线程t1111 在lockAgain方法中释放了锁
----线程t1111 在run方法中释放了锁
====线程t2222 在run方法中获取了锁
====线程t2222  在lockAgain方法中再次获取了锁
----线程t2222 在lockAgain方法中释放了锁
----线程t2222 在run方法中释放了锁
====线程t3333 在run方法中获取了锁
====线程t3333  在lockAgain方法中再次获取了锁
----线程t3333 在lockAgain方法中释放了锁
----线程t3333 在run方法中释放了锁
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class WaitTest {
    private Lock lock;
    private Condition condition;

    public WaitTest() {
        this.lock = new ReentrantLock();
        this.condition = this.lock.newCondition();
    }

    public void waitTest() {
        lock.lock();
        try {
            System.out.println("===线程"+Thread.currentThread().getName()+" 获取了锁");
            try {
                Thread.sleep(1000);
                System.out.println("========线程"+Thread.currentThread().getName()+"  调用await方法 等待并释放了锁");
                this.condition.await();
                System.out.println("========线程"+Thread.currentThread().getName()+"  await等待被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            System.out.println("---线程"+Thread.currentThread().getName()+"  释放锁");
            lock.unlock();
        }
    }

    public void signalTest() {
        lock.lock();
        try {
            System.out.println("===线程"+Thread.currentThread().getName()+"  获取了锁");
            System.out.println("-------线程"+Thread.currentThread().getName()+"  调用signal方法");
            this.condition.signal();
        } finally {
            System.out.println("---线程"+Thread.currentThread().getName()+"  释放锁");
            lock.unlock();
        }
    }

}

public class LockWaitTest {

    public static void newThread(WaitTest waitTest, String name, boolean isWait) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (isWait) waitTest.waitTest();
                else waitTest.signalTest();
            }
        },name).start();
    }

    public static void main(String[] args) {
        WaitTest waitTest = new WaitTest();
        newThread(waitTest, "t1111", true);
        newThread(waitTest, "t2222", false);
//        newThread(waitTest, "t3333", true);
//        newThread(waitTest, "t4444", false);
    }
}

输出结果是

===线程t1111 获取了锁
========线程t1111  调用await方法 等待并释放了锁
===线程t2222  获取了锁
-------线程t2222  调用signal方法
---线程t2222  释放锁
========线程t1111  await等待被唤醒
---线程t1111  释放锁

附录

    package java.util.concurrent.locks;

import java.util.Collection;
import java.util.concurrent.TimeUnit;

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    // ReentrantLock通过sync属性,实现独占锁的操作。
    // Sync是AbstractQueuedSynchronizer的子类
    private final Sync sync;


    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;


        // 加锁操作,又子类具体实现
        abstract void lock();


        // 非公平状态下获取锁
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            // 获取锁的记录状态state
            int c = getState();
            // 如果c==0表示当前锁是空闲的
            if (c == 0) {
                // 通过CAS原子操作方式设置锁的状态,如果为true,表示当前线程获取的锁,
                // 为false,锁的状态被其他线程更改,当前线程获取的锁失败
                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) {
            // c表示新的锁的记录状态
            int c = getState() - releases;
            // 如果当前线程不是独占锁的线程,就抛出IllegalMonitorStateException异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            // 标志是否可以释放锁
            boolean free = false;
            // 当新的锁的记录状态为0时,表示可以释放锁
            if (c == 0) {
                free = true;
                // 设置独占锁的线程为null
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively() {
            // 返回当前线程是不是独占锁的线程
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        final Thread getOwner() {
            // 返回独占锁的线程
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            // 只有当前线程是独占锁的线程,才会返回锁的记录状态state,否则返回0
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            // 返回锁是不是被使用状态
            return getState() != 0;
        }

        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    /**
     * 非公平锁
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        // 获取锁,如果没有获取到锁,则当前线程要阻塞等待
        final void lock() {
            // compareAndSetState返回true,表示当前线程获取锁成功。
            // 因为是非公平锁,所以不需要判断AbstractQueuedSynchronizer线程等待队列是否有值
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 调用acquire方法,获取锁
                acquire(1);
        }

        // 尝试获取锁,获取到锁返回true,没有获取到返回false
        protected final boolean tryAcquire(int acquires) {
            // 调用父类的nonfairTryAcquire方法
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * 公平锁
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            // 与非公平锁不同,因为它要考虑线程等待队列是否有值
            acquire(1);
        }

        // 尝试获取锁,与与非公平锁最大的不同就是调用hasQueuedPredecessors()方法
        // hasQueuedPredecessors方法返回true,表示等待线程队列中有一个线程在当前线程之前,
        // 根据公平锁的规则,当前线程不能获取锁。
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            // 获取锁的记录状态
            int c = getState();
            // 如果c==0表示当前锁是空闲的
            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;
        }
    }


    // 默认创建的是非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }


    // 根据fair值,决定创建公平锁还是非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    // 获取锁,如果获取不到,就一直等待。不响应中断请求
    public void lock() {
        sync.lock();
    }

    // 获取锁,如果获取不到,就一直等待。如果有中断请求就抛出异常
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    // 尝试用非公平锁方式去获取锁,立即返回。返回true表示获取成功,返回false表示获取失败
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    // 在规定的unit时间内获取锁,如果时间到了还没有获取到锁,则返回false,表示获取失败
    // 如果在线程等待期间有中断请求就抛出异常
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    // 当前线程释放占用的锁,并唤醒这个锁上的一个等待线程
    public void unlock() {
        sync.release(1);
    }

    // 创建一个Condition对象
    public Condition newCondition() {
        return sync.newCondition();
    }

    //
    public int getHoldCount() {
        return sync.getHoldCount();
    }

    // 当前线程是不是持有锁的线程
    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }

    // 锁是否已经被持有
    public boolean isLocked() {
        return sync.isLocked();
    }

    // 是不是公平锁
    public final boolean isFair() {
        return sync instanceof FairSync;
    }

    // 返回持有锁的线程,如果null,表示没有任何线程持有锁
    protected Thread getOwner() {
        return sync.getOwner();
    }

    // 是不是有等待锁的线程,即锁的同步队列是不是不为空
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    // 等待锁的线程队列中有没有thread线程
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }

    // 等待锁线程队列的长度
    public final int getQueueLength() {
        return sync.getQueueLength();
    }

    // 返回等到锁的线程队列的集合
    protected Collection<Thread> getQueuedThreads() {
        return sync.getQueuedThreads();
    }

    // condition对象的condition队列是否有等待线程
    public boolean hasWaiters(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    // condition对象上等待线程的个数
    public int getWaitQueueLength(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    // condition对象上等待线程的集合
    protected Collection<Thread> getWaitingThreads(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    public String toString() {
        Thread o = sync.getOwner();
        return super.toString() + ((o == null) ?
                                   "[Unlocked]" :
                                   "[Locked by thread " + o.getName() + "]");
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容