Java并发编程--基础进阶高级(完结)

 

Java并发编程--基础进阶高级完整笔记。

这都不知道是第几次刷狂神的JUC并发编程了,从第一次的迷茫到现在比较清晰,算是个大进步了,之前JUC笔记不见了,重新做一套笔记。

目录

:fire:1.多线程--基础内容

:fire:2.八锁现象(synchronized、static)

2.static synchronized

:fire:3.Java集合--安全性

:fire:4.高并发--辅助类

:fire:5.读写锁(ReadWriteLock)

1.集合--队列(阻塞队列、同步队列)

2.线程池基本概念(三大方法、七大参数、四种拒绝策略)

:fire:7.Stream(4个函数式接口、Lambda、异步回调)

1.饿汉模式(程序一启动就new,十分占内存)

2.懒汉模式(DCL模式:双重检测,需要的时候才new)

:fire:9.Volatile和Atomic

1.公平锁(FIFO):Lock锁可以自定义,synchronized

2.非公平锁(允许插队):Lock锁可以自定义

3.可重入锁(获取一个锁再获取其他锁不会造成死锁):lock锁和synchronized

4.自旋锁:得不到就一直等待(Atomic.getAndIncrement底层就是自旋锁)

:fire:1.多线程--基础内容

1.Thread状态

6种:新建、运行、阻塞、等待、超时等待、结束(可点击Thread查看源代码)

public enum State {

        NEW,

        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        TERMINATED;

    }

2.Synchronized

非公平锁

可重入锁,

Synchronized:是非公平锁(不能保证线程获得锁的顺序,即线程不会依次排队去获取资源,而是争抢,但是结果一定是正确的),是可重入锁(已获得一个锁,可以再获得锁且不会造成死锁,比如synchronized内部可以再写个synchronized函数)

/**

    * Author: HuYuQiao

    * Description: Synchronized实现方式(修饰函数即可)

    */

    class TicketSync{

        public int number = 50;

        //synchronized本质是队列,锁

        public synchronized void sale(){

            if(number > 0) {

                System.out.println(Thread.currentThread().getName() + "获得了第" + number-- +"票");

            }

        }

    }

3.Lock锁

可重入锁

公平还是不公平锁可以设置(默认不公平锁)

/**

    * Creates an instance of {@code ReentrantLock} with the

    * given fairness policy.

    *

    * @param fair {@code true} if this lock should use a fair ordering policy

    */

    public ReentrantLock(boolean fair) {

        sync = fair ? new FairSync() : new NonfairSync();

    }

Lock:加锁之后必须解锁,,否则其他线程就获取不到了,所以用try-catch-finally包起来。

/**

    * Author: HuYuQiao

    * Description: Lock实现方式(加锁、解锁)

    */

    class TicketLock{

        Lock lock = new ReentrantLock();

        public int number = 50;

        public void sale(){

            lock.lock();

            try {

                System.out.println(Thread.currentThread().getName() + "获得了第" + number-- +"票");

            } catch (Exception e) {

                e.printStackTrace();

            } finally {

                lock.unlock();

            }

        }

    }

4.总结

在不加锁情况下,多线程会争抢,导致输出顺序、计算结果都会不一致(上面例子结果如果一样是因为只有3个线程,for循环即出错,因为number--这个函数本身不是线程安全的),所以就引入了锁的概念,synchronized,lock保证了输出顺序、计算结果的一致性。

虚假唤醒:在synchronized.wait与lock.condition.await唤醒线程时,是从await代码之后开始运行,所以为了保证能唤醒线程,需要用while语句将代码包含起来。

完整代码

package com.empirefree.springboot;

import lombok.extern.slf4j.Slf4j;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

import sun.security.krb5.internal.Ticket;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

/**

* @program: springboot

* @description: 多线程

* @author: huyuqiao

* @create: 2021/06/26 14:26

*/

@RunWith(SpringRunner.class)

@SpringBootTest

@Slf4j

public class ThreadTest {


    /**

    * Author: HuYuQiao

    * Description: Synchronized实现方式(修饰函数即可)

    */

    class TicketSync{

        public int number = 50;

        //synchronized本质是队列,锁

        public synchronized void sale(){

            System.out.println(Thread.currentThread().getName() + "获得了第" + number-- +"票");

        }

    }


    /**

    * Author: HuYuQiao

    * Description: Lock实现方式(加锁、解锁)

    */

    class TicketLock{

        Lock lock = new ReentrantLock();

        public int number = 50;

        public void sale(){

            lock.lock();

            try {

                System.out.println(Thread.currentThread().getName() + "获得了第" + number-- +"票");

            } catch (Exception e) {

                e.printStackTrace();

            } finally {

                lock.unlock();

            }

        }

    }

    @Test

    public void testThread() {

//        TicketSync ticket = new TicketSync();

        TicketLock ticket = new TicketLock();

        new Thread( () ->{

            for (int i = 0; i < 50; i++) {

                ticket.sale();

            }

        },"ThreadA").start();

        new Thread(()->{

            for (int i = 0; i < 50; i++) {

                ticket.sale();

            }

        },"ThreadB").start();

        new Thread(()->{

            for (int i = 0; i < 50; i++) {

                ticket.sale();

            }

        },"ThreadC").start();

        for (int i = 0; i < 500; i++) {

            new Thread(() -> {

                ticket.sale();

            }).start();

        }

    }

}

:fire:2.八锁现象(synchronized、static)

即synchronized、static修饰的函数,执行顺序、输出结果。

结果表明:

1.synchronized修饰的函数:会锁住对象(可以看成锁对象中某个方法),看起来代码会依次执行,而没有锁的方法即不受影响,一来就先执行

2.static synchronized修饰的函数:会锁住类.class(可以不同对象访问的都是同一个函数),所以2个对象访问自己的函数依然还是顺序执行.

3.一个有static,一个没有static:即一个锁类.class,另一个锁对象,不管是同一个对象还是不同对象,就都不需要等待了,不会顺序执行。

1.synchronized

修饰函数会保证同一对象依次顺序执行()

class Phone{

    //synchronized

    public synchronized void sendSms() {

        try {

            TimeUnit.SECONDS.sleep(4);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("sendSms");

    }

    public synchronized  void call() {

        System.out.println("call");

    }

    public void playGame(){

        System.out.println("playGame");

    }

}

    public static void main(String[] args) {

        //Thread--代码执行顺序问题

        Phone phone = new Phone();

        new Thread(phone::sendSms, "A").start();

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        new Thread(phone::call, "B").start();

        new Thread(phone::playGame, "C").start();

    }

2.static synchronized

class PhoneStatic{

    //static synchronized

    public static synchronized void sendSmsStatic() {

        try {

            TimeUnit.SECONDS.sleep(4);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("sendSmsStatic");

    }

    public static synchronized  void callStatic() {

        System.out.println("callStatic");

    }

}

    public static void main(String[] args) {

        PhoneStatic phoneStatic = new PhoneStatic();

        PhoneStatic phoneStatic2 = new PhoneStatic();

        new Thread(() ->{

            phoneStatic2.sendSmsStatic();

        }, "A").start();

        try {

            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        new Thread(() ->{

            phoneStatic2.callStatic();

        }, "B").start();

    }

:fire:3.Java集合--安全性

//集合安全性--list,set,map都非线程安全

        List<String> list = new Vector<>();

        List<String> list = Collections.synchronizedList(new ArrayList<>());

        List<String> list = new CopyOnWriteArrayList<>();

        Map<String, String> objectObjectHashMap = new ConcurrentHashMap<>();

        Map<Object, Object> objectObjectHashMap1 = Collections.synchronizedMap(new HashMap<>());

        //set底层就是map:无论hashset还是linkedhashset

        Set<String> set = Collections.synchronizedSet(new LinkedHashSet<>());

        Set<String> set = new CopyOnWriteArraySet<>();

:fire:4.高并发--辅助类

1.countdownLatch

Countdownlatch:减一操作,直到为0再继续向下执行

package Kuangshen.JUC.Thread;

import java.util.concurrent.CountDownLatch;

public class countDownLatch {

    public static void main(String[] args) throws InterruptedException {

        final CountDownLatch countDownLatch = new CountDownLatch(5);

        for (int i = 0; i < 5; i++) {

            new Thread(() -> {

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

                countDownLatch.countDown();

                }, String.valueOf(i)).start();

        }

        countDownLatch.await(); //等待上述执行完毕再向下执行

        System.out.println("close door");

    }

}

2.cyclicbarrier

Cyclicbarrier:+1操作,对于每个线程都自动+1并等待,累计到规定值再向下执行,

package Kuangshen.JUC.Thread;

import java.util.concurrent.BrokenBarrierException;

import java.util.concurrent.CyclicBarrier;

/**

* @author :Empirefree

* @description:TODO

* @date :2021/2/10 10:56

*/

public class cyclicbarrier {

    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {

        //CyclicBarrier里面是容量 + runnable

        final CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () ->{

            System.out.println("不断增加到7即向后执行,与countdownlatch相反");

        }

      );

        /*对于指派的局部变量,lambda只能捕获一次 ,故而需定义成final(int内部定义就是final),而且线程中,

        不能对局部变量进行修改,如需要修改,需定义成原子类atomic

        */

        for (int i = 0; i < 7; i++) {

            int finalI = i;

            new Thread(() ->{

                System.out.println(finalI);

                try {

                    cyclicBarrier.await();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                } catch (BrokenBarrierException e) {

                    e.printStackTrace();

                }

            }).start();

        }

    }

}

3.semaphore

semaphore:对于规定的值,多个线程只规定有指定的值能获取,每次获取都需要最终释放,保证一定能互斥执行

package Kuangshen.JUC.Thread;

import java.util.concurrent.Semaphore;

import java.util.concurrent.TimeUnit;

/**

* @author :Empirefree

* @description:TODO

* @date :2021/2/10 15:24

*/

public class semaphore {

    public static void main(String[] args) {

        final Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 60; i++) {

            new Thread(() -> {

                try {

                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName() + "抢到车位");

                    TimeUnit.SECONDS.sleep(2);

                    System.out.println(Thread.currentThread().getName() + "离开车位");

                } catch (InterruptedException e) {

                    e.printStackTrace();

                } finally {

                    semaphore.release();

                }

            }).start();

        }

    }

}

:fire:5.读写锁(ReadWriteLock)

ReadWriteLock也是多线程下的一种加锁方式,下面列出ReadWriteLock和synchronized对多线程下,保证读完之后在写的实现方式

//读写锁 :写只有一个线程写,写完毕后 可以多个线程读

MyCache myCache = new MyCache();

int num = 6;

for (int i = 1; i < num; i++) {

    int finalI = i;

    new Thread(()->{

        myCache.write(String.valueOf(finalI),String.valueOf(finalI));

    },String.valueOf(i)).start();

}

for (int i = 1; i < num; i++) {

    int finalI = i;

    new Thread(()->{

        myCache.read(String.valueOf(finalI));

    },String.valueOf(i)).start();

}

class MyCache{

    private volatile Map<String,String> map = new HashMap<>();

    private ReadWriteLock lock = new ReentrantReadWriteLock();

    //存,写

    public void write(String key,String value){

        lock.writeLock().lock();    //写锁

        try {

            System.out.println(Thread.currentThread().getName()+"线程开始写入");

            map.put(key,value);

            System.out.println(Thread.currentThread().getName()+"线程开始写入ok");

        } catch (Exception e){

            e.printStackTrace();

        } finally {

            lock.writeLock().unlock();

        }

    }

    //取,读

    public void read(String key){

        lock.readLock().lock();    //读锁

        try {

            System.out.println(Thread.currentThread().getName()+"线程开始读取");

            map.get(key);

            System.out.println(Thread.currentThread().getName()+"线程读取ok");

        } catch (Exception e){

            e.printStackTrace();

        } finally {

            lock.readLock().unlock();

        }

    }

    //存,写

    public synchronized void writeSync(String key,String value){

        try {

            System.out.println(Thread.currentThread().getName()+"线程开始写入");

            map.put(key,value);

            System.out.println(Thread.currentThread().getName()+"线程开始写入ok");

        } catch (Exception e){

            e.printStackTrace();

        }

    }

    //取,读

    public void readSync(String key){

        try {

            System.out.println(Thread.currentThread().getName()+"线程开始读取");

            map.get(key);

            System.out.println(Thread.currentThread().getName()+"线程读取ok");

        } catch (Exception e){

            e.printStackTrace();

        }

    }

}

:fire:6.线程池

1.集合--队列(阻塞队列、同步队列)

阻塞队列:blockingQueue(超时等待--抛弃,所以最后size=1)

//阻塞队列

        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);

        arrayBlockingQueue.offer("a", 2, TimeUnit.SECONDS);

        arrayBlockingQueue.offer("a", 2, TimeUnit.SECONDS);

        System.out.println("超时等待==" + arrayBlockingQueue.size());

同步队列:synchronizeQueue(类似于生产者消费者)

//同步队列

        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();

        new Thread(()->{

            try {

                System.out.println(Thread.currentThread().getName()+"put 01");

                synchronousQueue.put("1");

                System.out.println(Thread.currentThread().getName()+"put 02");

                synchronousQueue.put("2");

                System.out.println(Thread.currentThread().getName()+"put 03");

                synchronousQueue.put("3");

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }).start();

        new Thread(()->{

            try {

                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());

                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());

                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }).start();

2.线程池基本概念(三大方法、七大参数、四种拒绝策略)

三大方法:newSingleThreadExecutro(单个线程),newFixedThreadPool(固定大小线程池),newCachedThreadPool(可伸缩)

ExecutorService threadPool = Executors.newSingleThreadExecutor();

        ExecutorService threadPool2 = Executors.newFixedThreadPool(5);

        ExecutorService threadPool3 = Executors.newCachedThreadPool();

七大参数:

ThreadPoolExecutor(int corePoolSize,  //核心线程池大小

                          int maximumPoolSize, //最大的线程池大小(当阻塞队列满了就会打开)

                          long keepAliveTime,  //(空闲线程最大存活时间:即最大线程池中有空闲线程超过这个时间就会释放线程,避免资源浪费)

                          TimeUnit unit, //超时单位

                          BlockingQueue<Runnable> workQueue, //阻塞队列

                          ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动

                          RejectedExecutionHandler handler //拒绝策略

四种拒绝策略:

new ThreadPoolExecutor.AbortPolicy: // 该 拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常

new ThreadPoolExecutor.CallerRunsPolicy(): // //该拒绝策略为:哪来的去哪里 main线程进行处理

new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。

new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

//异步回调--无返回值

        CompletableFuture<Void> future = CompletableFuture.runAsync(() ->{

            try {

                TimeUnit.SECONDS.sleep(2);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

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

        });

        System.out.println("begin");

        System.out.println(future.get());

        System.out.println("end");

        //异步回调--有返回值

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() ->{

            try {

                TimeUnit.SECONDS.sleep(2);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            return 3;

        });

        System.out.println("begin");

        System.out.println(future2.get());

        System.out.println(future2.whenComplete((result, error) ->{

            System.out.println("返回结果:" + result);

            System.out.println("错误结果" + error);

        }).exceptionally(throwable -> {

            System.out.println(throwable.getMessage());

            return 502;

        }).get());

        System.out.println("end");

:fire:8.单例模式

1.饿汉模式(程序一启动就new,十分占内存)

/**

* @program: untitled

* @description: 单例模式

* @author: huyuqiao

* @create: 2021/06/27 14:44

*/

public class SingleModel {

    /*

    * 可能会浪费空间

    * */

    private byte[] data1 = new byte[1024*1024];

    private byte[] data2 = new byte[1024*1024];

    private byte[] data3 = new byte[1024*1024];

    private byte[] data4 = new byte[1024*1024];

    private static final SingleModel hugrySingle = new SingleModel();

    private SingleModel(){

    }

    public static SingleModel getInstance(){

        return hugrySingle;

    }

}

2.懒汉模式(DCL模式:双重检测,需要的时候才new)

1.第一层if没有加锁,所以会有多个线程到达if

2.如果内部new对象指令重排,就会导致有些线程认为lazyManModel有对象,所以会直接返回lazyManModel(实际为null)

3.所以需要加上valitile防止指令重排

/**

* @program: untitled

* @description: 懒汉式

* @author: huyuqiao

* @create: 2021/06/27 15:06

*/

public class LazyManModel {

    private volatile static LazyManModel lazyManModel;

    private LazyManModel(){

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

    }

    //DCL懒汉式:双重检测锁--实现效果,只有为空的才null,否则不用null,所以需要2重if判断。

    public static LazyManModel getInstance(){

        if (lazyManModel == null){

            synchronized (LazyManModel.class){

                if (lazyManModel == null){

                    lazyManModel = new LazyManModel();

                }

            }

        }

        return lazyManModel;

    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {

            new Thread(() ->{

                LazyManModel.getInstance();

            }).start();

        }

    }

}

:fire:9.Volatile和Atomic

如果感觉小编写得不错,请素质三连:点赞+转发+关注。我会努力写出更好的作品分享给大家。更多学习资料小编已打包好,可以找我领取哦!领取方式:私信回复暗号【444】即可免费领取更多完整版资料。

:fire:10.Java中锁

1.公平锁(FIFO):Lock锁可以自定义,synchronized

2.非公平锁(允许插队):Lock锁可以自定义

3.可重入锁(获取一个锁再获取其他锁不会造成死锁):lock锁和synchronized

4.自旋锁:得不到就一直等待(Atomic.getAndIncrement底层就是自旋锁)

import java.util.Collections;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicInteger;

import java.util.concurrent.atomic.AtomicReference;

import java.util.concurrent.locks.ReentrantLock;

/**

* @program: untitled

* @description: spinlock

* @author: huyuqiao

* @create: 2021/06/27 15:40

*/

public class SpinLockTest {

    public static void main(String[] args) throws InterruptedException {

        //使用CAS实现自旋锁

        SpinlockDemo spinlockDemo=new SpinlockDemo();

        new Thread(()->{

            spinlockDemo.myLock();

            try {

                TimeUnit.SECONDS.sleep(3);

            } catch (Exception e) {

                e.printStackTrace();

            } finally {

                spinlockDemo.myUnlock();

            }

        },"t1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{

            spinlockDemo.myLock();

            try {

                TimeUnit.SECONDS.sleep(3);

            } catch (Exception e) {

                e.printStackTrace();

            } finally {

                spinlockDemo.myUnlock();

            }

        },"t2").start();

    }

}

class SpinlockDemo {

    // 默认

    // int 0

    //thread null

    AtomicReference<Thread> atomicReference=new AtomicReference<>();

    //加锁

    public void myLock(){

        Thread thread = Thread.currentThread();

        System.out.println(thread.getName()+"===> mylock");

        //自旋锁--为空则返回true,否则返回false

        while (!atomicReference.compareAndSet(null,thread)){

            System.out.println(Thread.currentThread().getName()+" ==> .自旋中~");

        }

    }

    //解锁

    public void myUnlock(){

        Thread thread=Thread.currentThread();

        System.out.println(thread.getName()+"===> myUnlock");

        atomicReference.compareAndSet(thread,null);

    }

}

5.死锁命令排查

jps -l

jstack 进程号

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

推荐阅读更多精彩内容