Java多线程相关知识点

线程基本知识

  1. 什么是线程安全性?
    当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么可以认为这个类是线程安全的。
    在线程安全的类中封装了必要的同步机制,因此使用它时无需再考虑同步

  2. 什么是线程?和进程有什么区别?
    线程是操作系统能够进行运算调度的最小单元,被包含在进程中,是进程的实际运行单元。
    线程是进程的子集,不同的进程使用不同的内存空间,而所有线程共享相同内存空间。

  3. 线程中的状态有哪些?
    新建(new) 可运行(runnable) 运行(running) 阻塞(block) 死亡(dead)

  4. 多线程有什么用?

  • 发挥多核CPU的优势
  • 单核应用多线程,防止阻塞
  • 便于建模
  1. Java中如何使用线程?不同场景如何使用?
    Thread类实例就是一个线程,但是它需要调用Runnable接口来执行线程任务,因此使用线程可以继承Thread类或者实现Runnable接口来实现线程。如果已经继承了其他类,那么只能选择实现Runnable接口。
    Thread类内的start方法用于启动新线程,其内部调用了run方法,但是如果直接调用run方法,只会在原线程中调用,而没有新线程启动。

  2. 多次start一个线程会怎么样?为什么?
    start方法会真正开启一个线程,threadStatus从NEW变为RUNNABLE,start之前会检查threadStatus是否为NEW,否则所有调用会出错illeaglthreadstateexception。

  3. 线程运行时发生异常会怎么样?如何停止一个运行的线程?
    如果异常没有被捕获则线程停止,Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉.换句话说,我们不能捕获从线程中逃逸的异常。
    目前没有一个兼容且线程安全的方法来停止线程,当run或者call方法执行完毕后线程会自动结束,如果需要手动结束线程,可以用volatile变量来退出run或者取消任务来中断线程。

  4. 线程类的构造方法、静态块是被哪个线程调用的?
    线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

线程使用

  1. Runnable和Callable有什么不同?
    Callable中的call方法可以返回值和抛出异常,还可以返回装载有计算结果的Future对象,而Runnable则没有这些功能。

  2. Java中CyclicBarrier 和 CountDownLatch有什么不同?
    CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
    而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
    另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

线程同步

  1. 什么是竞态条件?
    竞态条件是指由于不恰当的执行时序而出现不正确的结果的情况。
    多线程对一些资源的竞争会产生竞态条件,因为线程间的随机竞争会导致程序在并发下出现问题。
    引发竞态条件的原因一般有两种:操作非原子性,先检查后执行;
    对于基本类型的增减操作,是读取-修改-写入的三个独立操作的序列,其结果依赖之前的状态。
    先检查后执行,在检查和执行之间,原来的观察结果可能变得无效,从而导致问题。

  2. 什么是原子操作?
    原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断。

  3. 如何在两个线程间共享数据?
    通过共享对象或者是使用像阻塞队列这样并发的数据结构

  4. Thread类中的yield方法有什么作用?
    Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用,而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停
    状态后马上又被执行。

  5. Java多线程中调用wait()和 sleep()方法有什么不同?
    wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时,它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

  6. Java中notify 和 notifyAll有什么区别?为什么这些方法不在thread类里面?为什么要在同步块中调用?
    锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
    等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.
    JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。
    为了避免wait和notify之间产生竞态条件

  7. Java中interrupted 和 isInterrupted方法的区别?
    interrupted()和isInterrupted()的主要区别是前者会将中断状态清除而后者不会。

  8. 多线程中的忙循环是什么?
    忙循环就是用循环让一个线程等待,不像传统方法wait(), sleep()或
    yield()它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

  9. 为什么应该在循环中检查等待条件?
    处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify
    ()方法调用之后和等待线程醒来之前这段时间等待状态可能会改变。

  10. 如何避免死锁?死锁和活锁的区别?
    死锁满足以下条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
    避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)等操作来避免死锁。
    处于活锁的线程或进程的状态是不断改变的,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。
  1. synchronized锁普通方法和锁静态方法有什么异同?
    静态方法上的锁是锁住这个类的,普通方法上的锁是锁住这个对象的。类锁和对象锁不同,他们之间不会产生互斥。

  2. Java中synchronized 和 ReentrantLock 有什么不同?

  • ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了公平锁,定时锁等候和中断锁等
  • synchronized是在JVM层面上实现的,在代码执行时出现异常,JVM会自动释放锁定,但是lock是通过代码实现解锁的
  1. Java中的ReadWriteLock是什么?
    一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程 的情况下一个读锁可能会同时被多个读线程持有。写锁是独占的。实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

  2. 什么是自旋锁?
    自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,如果做了多次忙循环发现还没有获得锁,再阻塞。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

  1. Java中的同步集合和并发集合的区别?
    同步集合和并发集合都为并发和多线程提供了合适的线程安全集合,不过并发集合的可扩展性更高。1.5并发集合更像ConcurrentHashMap,线程安全提供了锁分离,内部分区,CAS算法等技术。

  2. 什么是ThreadLocal变量?
    每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。
    首先,通过复用减少了代价高昂的对象的创建个数。
    其次,在没有使用高代价的同步或者不变性的情况下获得了线程安全。

  3. volatile 变量和 atomic 变量有什么不同?
    Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前
    , 但它并不能保证原子性。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一。

  4. 什么是不可变对象,它对写并发应用有什么帮助?如何创建不可变对象?
    Immutable对象保证了对象的内存可见性。可以在没有同步的情况下共享,降低了对该对象进行并发访问时的同步化开销。创建不可变对象步骤:

  • 将所有的成员声明为私有的
  • 通过构造方法初始化所有成员
  • 对变量不要提供setter方法
  • 在getter方法中,返回对象的拷贝。
  1. 什么是CAS? 什么是AQS?
  • CAS,全称为Compare and Swap,即比较-替换,是java.util.concurrent的基础。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。
  • AQS,全称为AbstractQueuedSychronizer(抽象队列同步器),是整个Java并发包的核心,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS实际上以双向队列的形式连接所有的Entry,比方说ReentrantLock,所有等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。AQS定义了对双向队列所有的操作,而只开放了tryLock和tryRelease方法给开发者使用,开发者可以根据自己的实现重写tryLock和tryRelease方法,以实现自己的并发功能。

线程池

  1. 什么是线程池? 为什么要使用它?
    假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
    一个线程池包括以下四个基本组成部分:
    1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
    2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
    3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
    4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段。线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目

  1. 常用的线程池有几种?这几种线程池之间有什么区别和联系?线程池的实现原理是怎么样的?
    通常使用Executors 获取线程池,常用的线程池有以下几种:
(1)CachedThreadPool :
  • 按需创建新的线程,如果没有可用线程则创建新的线程,之前用过的线程可能会再次被使用;
  • 因为空闲线程会被移除线程池,因此,如果线程池长时间不被使用也不会消耗系统资源
(2)FixedThreadPool :
  • 在任何情况下最多只有nThread个线程工作,多余的Task将会被存放到队列中等待;
  • 如果线程在执行任务中被终止,终止之前会创建其他的线程代替原来的;
  • 线程将会一直存在在线程池中,直到调用shutDown()方法
(3)ScheduledThreadPool :
  • 核心线程数将会一直存在线程池中,除非设置了allowCoreThreadTimeOut
  • 可以设置线程的执行时间
(4)SingleThreadExecutor:
  • 可保证顺序地执行各个任务
  • 任意给定的时间不会有多个线程是活动的 。
实现原理

以上线程池都是是调用了ThreadPoolExecutor这个类的构造,也就是说当我们得到的ExecutorService实际是ThreadPoolExecutor的实例。


image
  1. 高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
  • 高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
  • 并发不高、任务执行时间长的业务要区分开看:
    a. IO密集型任务: 因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
    b. CPU密集型任务:这个就没办法了,和1一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
  • 并发高、业务执行时间长:解决这种类型任务的关键不在于线程池而在于整体架构的设计,第一步业务里数据做缓存,第二步增加服务器,最后引入中间件,对任务进行拆分和解耦
  1. Java线程池中submit()和 execute()方法有什么区别?
    execute()方法的返回类型是void,它定义在Executor接口中,
    而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口

  2. 什么是阻塞式方法?
    阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

  3. 什么是FutureTask?
    FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。
    Future: 只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。
    Runnable:可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口,所以它可以提交给Executor来执行。

  4. 提交任务时线程池队列已满,此时会发生什么?
    Java线程池会将提交的任务先置于工作队列中,在从工作队列中获取。那么工作队列就有两种实现策略:无界队列和有界队列。无界队列不存在饱和的问题,但是其问题是当请求持续高负载的话,任务会无脑的加入工作队列,那么很可能导致内存等资源溢出或者耗尽。而有界队列不会带来高负载导致的内存耗尽的问题,但是有引发工作队列已满情况下,线程池按工作队列饱和策略处理这种情况。
    饱和策略分为:Abort 策略(默认), CallerRuns 策略,Discard策略,DiscardOlds策略。

  5. Java中的fork join框架是什么?
    fork join框架是专门为了那些可以递归划分成许多子模块设计的,目的是将所有可用的处理能力用来提升程序的性能。优势是它使用了工作窃取算法,可以完成更多任务的工作线程可以从其它线程中窃取任务来执行。

JVM中的线程

  1. Java内存模型是什么?
    Java内存模型规定Java程序在不同的内存架构,CPU和操作系统间有确定性行为。这为一个内存所做的变动能被其他线程可见提供了保证,它们之间是先行发生关系,确保了:
  • 线程内的代码按先后顺序执行——程序次序规则
  • 线程的所有操作必须在线程的start调用之后——线程启动规则
  • 线程的所有操作都会在线程终止之前结束——线程终止规则
  • 对象的终结操作必须在这个对象构造完成之后——对象终结规则
  • 前一个对volatile的写在后一个volatile读之前——volatile变量规则
  • 解锁操作必须发生在另一个锁定操作之前——管程锁定规则
  1. Java中堆和栈有什么不同?
    因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。
    而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile变量就可以发挥作用了。它要求线程从主存中读取变量的值,不从工作内存的缓存变量中读取。

  2. 如何获取线程堆栈(用于排查死锁)?如何控制线程的栈堆大小?
    在Windows可以使用Ctrl +Break组合键来获取线程堆栈,Linux下用kill
    -3命令。
    也可以用jps找到线程id,在用jstack获取具体线程堆栈。
    -Xss参数用来控制线程的堆栈大小

  3. Java中用到的线程调度算法是什么
    抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

  4. Thread.sleep(0)的作用是什么
    由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。

实践

  1. 生产者消费者模型的作用是什么?
  • 通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率
  • 解耦,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约
  1. 如何写代码来解决生产者消费者问题?
    比较低级的办法是用wait和notify来解决这个问题,比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型

  2. 有三个线程T1,T2,T3,怎么确保它们按顺序执行?
    可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
    或者用conditions的await和signal和指定唤醒特定线程。

  3. 假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?

  • 用concurrent包下的CountDownLatch
  • 用concurrent包下的CyclicBarrier
  1. Linux环境下如何查找哪个线程使用CPU最长
  • 获取项目的pid,jps或者ps -ef | grep java
  • 使用"top -H -p pid"+"jps pid",打印出当前的项目,每条线程占用CPU时间的百分比。
  1. 如何写出线程安全的单例模式?
    http://blog.csdn.net/cselmu9/article/details/51366946
  • 饿汉(推荐
    private static MySingleton instance = new MySingleton(); 
    private MySingleton(){}  
    public static MySingleton getInstance() {  return instance;  }  
  • static代码块
    private static MySingleton instance = null;  
    private MySingleton(){}  
    static{ instance = new MySingleton();  }  
    public static MySingleton getInstance() {  return instance; }  
  • 枚举类
public class ClassFactory{   
    private enum MyEnumSingleton{  
        singletonFactory;  
        private MySingleton instance;  
        //枚举类的构造方法在类加载是被实例化 
        private MyEnumSingleton(){ instance = new MySingleton();}  
        public MySingleton getInstance(){ return instance; }  
    }   
    public static MySingleton getInstance(){ return MyEnumSingleton.singletonFactory.getInstance(); }  
}  
  
class MySingleton{//需要获实现单例的类,比如数据库连接Connection  
    public MySingleton(){}   
}  
  • 双检锁
    //使用volatile关键字保其可见性  
    volatile private static MySingleton instance = null;  
    private MySingleton(){}  
    public static MySingleton getInstance() {  
        try {  //懒汉式  
         if(instance != null){}else{  
                Thread.sleep(300); //创建实例之前可能会有准备工作
                synchronized (MySingleton.class) {  
                    if(instance == null){//二次检查  
                        instance = new MySingleton();   }   }  }   
        } catch (InterruptedException e) { e.printStackTrace(); }  
        return instance;  
    } 
  1. 写出3条你遵循的多线程最佳实践
  • 给线程起个有意义的名字。
  • 避免锁定和缩小同步的范围
  • 多用同步类少用wait 和 notify
  • 多用并发集合少用同步集合
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,696评论 0 11
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,216评论 11 349
  • 教育问题置于中国,更是影响国家未来的头等大事,故先人有言:教化大事,圣人之功也!特别是对于我们这个以血缘关系为文...
    木子口儿阅读 181评论 0 1
  • 我蜷缩在房间里,房间里是绿色小清新的布局,是我喜欢的格调,除了广州的天气和男朋友身上的肥肉,其余的一切都还好,觉得...
    娜姆阅读 441评论 0 1