Java多线程

线程创建有4种方式 线程同步有3种方式

程序、进程、线程

  • 一个java应用程序至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程(main执行的同时,有失效的局部变量会被垃圾回收线程回收)
  • 并行与并发
    并行:多个CPU同时处理多个任务
    并发:一个CPU同时(划分时间片)处理多个任务

线程的创建和使用(四种之前两种)

1️⃣继承java.lang.Thread类
  • 方式一:继承于Thread类,重写run方法,将此线程应该做的事情写在run的方法体里,创建Thread类的子类对象,调用start()
class MyThread extends Thread{
   @Override
   public void run() {
       for(int i=0;i<101;i++){
           System.out.println(i);
       }
   }
}


public class ThreadTest{
   public static void main(String[] args) {
       MyThread myThread=new MyThread();
       //start:启动当前线程,调用当前线程的run方法
       myThread.start();
       for(int i=0;i<132;i++){
           System.out.println("hello");
       }
   }
}
  • 方式二:创建Thread类的匿名子类(只用一次)
public class ThreadTest2 {
    
    //两个线程,一个输出奇数,一个输出偶数
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for(int i=0;i<=100;i++){
                    if(i%2==0){
                        System.out.println(i); 
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for(int i=0;i<=100;i++){
                    if(i%2!=0){
                        System.out.println(i);
                    }
                }
            }
        }.start();
        
    }
}
Thread类的常用方法
  • void start() 启动当前线程,调用当前线程的run方法
  • run() 需要重写,
  • String getName() 获取当前线程的名字
  • void setName(String name) 设置当前线程的名字,也可以通过构造器来设置
  • static Thread currentThread() 返回执行当前代码的线程
  • yield() 释放当前CPU的执行权,可能被其他线程抢占,但是也可能仍被当前线程抢占
public class ThreadMethod {
    public static void main(String[] args) {
        HelloThread helloThread=new HelloThread("线程一");
        //helloThread.setName("线程一");
        helloThread.start();

        //给主线程命名
        Thread.currentThread().setName("主线程");

        for(int i=0;i<=100;i++){
            if(i%2!=0)
                System.out.println(Thread.currentThread().getName()+":"+i);
            //这里也加上更容易看出效果
            if(i%20==0){
                Thread.currentThread().yield();
            }
        }

    }
}


class HelloThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<=100;i++){
            if(i%2==0)
                System.out.println(Thread.currentThread().getName()+":"+i);

            //释放当前CPU的执行权
            if(i%20==0){
                yield();
            }
        }
    }

    public HelloThread() {
    }

    public HelloThread(String name) {
        super(name);
    }
}
  • join(): 在线程A中调用B的join方法,线程A进入阻塞状态,直到线程B完全执行完之后,线程A才结束阻塞状态,等待CPU分配资源继续执行
    场景:当前线程需要另外一个线程执行完之后的数据,所以对停下来,等待另一个线程执行完之后,再继续执行
public static void main(String[] args) {
        HelloThread helloThread=new HelloThread();
        helloThread.start();

        for(int i=0;i<=100;i++){
            if(i%2!=0)
                System.out.println(Thread.currentThread().getName()+":"+i);
            //join
            if(i==19) {
                try {
                    helloThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
  • stop()强制结束线程的生命周期,不推荐使用
  • sleep(long millitime) 毫秒,将当前线程阻塞指定时间,然后恢复,等待CPU分配资源
    注意问题:
    1> 会抛出异常,但是只能使用try-catch的方式,不能使用throws,因为run方法是继承来的,父类的run方法没有抛出异常,所以子类重写的run也不能抛出异常
    2>是一个静态方法,可以直接Thread.sleep(i)
    应用:倒计时
  • isAlive() 判断当前线程是否存活
2️⃣实现Runnable接口
  • 方式:创建一个实现了Runnable接口的实现类,实现接口的run方法;创建实现类的对象;将此对象作为参数,传递到Thread类的构造器中,创建Thread类的对象;通过Thread类的对象调用start()
public class ThreadTestInterface {
    public static void main(String[] args) {
        MyRun myRun=new MyRun();
        //start()①启动线程 ②调用当前线程的run方法
        /*
         *源码:
         * private Runnable target;
         * public void run(){
         *     if(target!=null){
         *          target.run();
         *      }
         * }
         *
         */
        new Thread(myRun).start();
    }
}

class MyRun implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            if(i%2==0) System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

🤨两种方法的比较

开发中优先选择实现Runnable接口的方式:
①实现的方式没有类的单继承的局限性
②实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread类本身也实现了Runnable接口,所以相同点是两种方法都需要重写run方法,将线程需要执行的逻辑声明在run()中

线程的调度(优先级)

  • CPU的调度策略:时间片,抢占式(高优先级的线程抢占CPU)
  • Java调度方法:
    1>同优先级组成先进先出队列,使用时间片策略
    2>高优先级使用优先调度的抢占策略
  • 线程优先级:static final
    MAX_PRIORITY: 10 (先被执行的概率高,而不是一定会先被全部执行完)
    MIIN_PRIORITY:1
    NORM_PRIORITY:5 (默认情况下)
  • getPriority():返回线程优先级
    setPriority(int newPriority):改变线程的优先级,start之前设置线程的优先级
每日一考

线程的生命周期

Thread.state类记录了线程的五种状态

  • 新建:Thread类或子类的对象被声明并且创建时,新生的线程对象处于新建状态
  • 就绪:被start后,进入线程队列等待CPU时间片,此时已经具备了运行的条件,只是没有分配到CPU资源
  • 运行:当就绪的线程被调度获得CPU资源时,进入运行状态,run方法线程的操作和功能
  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行
  • 死亡:线程完成了全部工作或线程被提前强制性地中止,或出现异常导致结束


    线程的生命周期

线程的同步

多个线程操作共享数据的时候,可能会产生线程安全问题,通过同步机制解决线程安全问题
同步的方式,解决了线程安全的问题,但操作同步代码时,只能有一个线程参与,其他线程等待,相当于一个单线程过程,效率会低一点

🧡方法一:同步代码块

关键字:synchronized(同步监视器){需要被同步的代码}
1>需要被同步的代码:操作共享数据的代码,注意不能包含代码少了(用到了共享数据必须包进去),也不能包含代码多了(变成单线程事小,导致代码逻辑错误事大)
2>共享数据:多个线程共同操作的变量
3>同步监视器:俗称,锁。任何一个类的对象,都可以充当锁,但❗要求多个线程必须要共用同一把锁

  • 实现Runnable的方法
/**
 * @Author: ssy
 * @Description:  创建三个窗口买票。总票数100。解决线程同步问题
 * @Date: Created in 14:51 2020/11/11
 * @Modified By:
 */
public class WindowTest {
    public static void main(String[] args) {

        Window window1=new Window();
        Thread thread1=new Thread(window1);
        Thread thread2=new Thread(window1);
        Thread thread3=new Thread(window1);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();;
        thread2.start();
        thread3.start();
        //输出乱序是因为输出到控制台上需要时间,实际上票是按照顺序产生的

    }
}


class Window implements Runnable{
    private int ticket=100;   //三个窗口一共只有100张票
    Object object=new Object();   //同步监视器
    @Override
    public void run() {
        while (true){
            synchronized(object){   //包多了导致程序结果出错的情况:把while(true)也包进去了
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"窗口买票:票号为"+ticket);
                    ticket--;
                }
                else  break;
            }

        }
    }
}
  • 继承Thread的方法
/**
 * @Author: ssy
 * @Description: 买票,三个窗口一共卖100张票,一定要注意同步监视器需要使用的是同一个对象
 * @Date: Created in 9:55 2020/11/12
 * @Modified By:
 */
public class WindowTest2 {
    public static void main(String[] args) {

        Window2 thread1=new Window2();
        Window2 thread2=new Window2();
        Window2 thread3=new Window2();

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();;
        thread2.start();
        thread3.start();
        //输出乱序是因为输出到控制台上需要时间,实际上票是按照顺序产生的

    }
}


class Window2 extends Thread{
    private static int ticket=100;   //三个窗口一共只有100张票
    static Object object=new Object();   //同步监视器   static 静态变量
    @Override
    public void run() {
        while (true){
            synchronized(object){
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"窗口买票:票号为"+ticket);
                    ticket--;
                }
                else  break;
            }

        }
    }
}
  • 实现Runnable方法的简化(应该时刻注意,锁应该是共用的同一个)
synchronized(this){   //this是当前对象,对于实现Runnable的方法来说,this是多线程的同一个对象
        //同步代码块
}
  • 继承Thread方法的简化(反射相关知识,类也是对象,类只会加载一次,所以可以作为锁使用)
synchronized(Window2.class){   //Window2是继承了Thread类的子类,见上面的抢票代码
      
}
🧡方法二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,不妨将此方法声明为同步的。
1>同步方法仍然涉及到同步监视器,只是不需要显式声明,使用的是默认的
2>非静态的同步方法(Runnable),默认的同步监视器是this
3>静态的同步方法(Thread),默认的同步监视器是:当前类本身

/**
 * @Author: ssy
 * @Description:  使用同步方法解决实现Runnable的同步问题
 * @Date: Created in 10:22 2020/11/12
 * @Modified By:
 */
public class WindowTest3 {
    public static void main(String[] args) {

        Window3 window1= new Window3();
        Thread thread1=new Thread(window1);
        Thread thread2=new Thread(window1);
        Thread thread3=new Thread(window1);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();;
        thread2.start();
        thread3.start();
        //输出乱序是因为输出到控制台上需要时间,实际上票是按照顺序产生的

    }
}


class Window3 implements Runnable{
    Object object=new Object();   //同步监视器
    private int ticket=100;   //三个窗口一共只有100张票

    @Override
    public  void run() {
        while (true){
            if(saleTicket()==0) break;    //因为有while的存在,不能直接在run方法上加synchronized
                                          //同样因为while的存在,要注意如何提出方法,是否需要返回值,能够使循环结束
        }
    }

    private synchronized int saleTicket(){  //默认同步监视器就是 this  (继承Thread的方法慎用,应该加上static,同步监视器xxxx.class)
        if(ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"窗口买票:票号为"+ticket);
            ticket--;
            return 1;
        }
        else return 0;
    }
}

  • 单例懒汉模式的线程同步
/**
 * @Author: ssy
 * @Description:   单例  懒汉  高校模式
 * @Date: Created in 11:24 2020/11/12
 * @Modified By:
 */
public class BankTest {
}

class Bank{
    private Bank(){}
    
    private static Bank instance=null;
    
    public static Bank getInstance(){
        //方式一
        
        /*
         *
         *  synchronized (Bank.class){
                if(instance==null)
                    instance=new Bank();
                 return instance;
            }
         *
         */
        
        
        //方式二: 高效
        if(instance==null){    //不需要每次都走一遍synchronized
            synchronized (Bank.class){
                if(instance==null)
                    instance=new Bank();
            }
        }
        return instance;
    }

}

🧡方法三:Lock锁(JDK5.0新增)
  • 从JDK5.0开始,java提供了更强大的线程同步机制,通过显示定义同步锁对象来实现同步。同步锁使用lock锁实现
  • java.util.concurrent.locks.Lock接口
  • ReentrantLock实现了Lock接口,拥有与synchronized相同的并发性和内存语义。需要显式地加锁,释放锁
ReentrantLock lock = new ReentrantLock();//参数fair,无参方式默认为false;显式设置为true,则线程遵守先进先出规则
lock.lock();
/*
同步代码块,lock锁住,保证同步代码在锁住的期间是一个单线程
*/
lock.unlock();
  • 注意问题:synchronized需要注意使用的是不是一个同步监视器,同样,lock需要注意使用的是否为同一把锁
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: ssy
 * @Description:   三个窗口同时卖票  多线程  线程安全  lock锁
 * @Date: Created in 17:22 2020/11/12
 * @Modified By:
 */
public class WindowTest4 {
    public static void main(String[] args) {
        Window4 window=new Window4();

        Thread thread1=new Thread(window);
        Thread thread2=new Thread(window);
        Thread thread3=new Thread(window);


        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}


class Window4 implements Runnable{
    private ReentrantLock lock=new ReentrantLock();
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            try{

                lock.lock();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+":票号"+ticket);
                    ticket--;
                }
                else break;
            }finally {
                lock.unlock();
            }

        }
    }
}

  • 面试题1:如何解决线程安全问题?有几种方式?
    答:如上
  • 面试题2:synchronized和lock的异同点?
    相同点:都可以用来解决线程安全问题。
    不同:①Lock是显式锁,需要手动启动(lock()),手动结束(unlock()),synchronized是隐式锁,执行完相应的同步代码之后,自动释放同步监视器。
    ②Lock只有代码块锁,synchronized有代码块锁和方法锁。
    ③使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)。
  • 优先使用顺序:Lock -> 同步代码块 ->同步方法

线程的通信

  • 不同的线程操作共享数据,有一些“交流”,都可以看作是线程的通信
  • import java.lang.Object
  • wait():使调用该方法的线程进入阻塞状态,并释放同步监视器
  • notify():唤醒一个wait的线程。如果多个线程被wait,则唤醒优先级高的
  • notifyAll():唤醒所有的wait的线程
  • 注意的问题:上述三个方法只能用在同步代码块或者同步方法(synchronized)里,不能使用在Lock锁中。三个方法的调用者是同步监视器
  • 例题:使用两个线程打印1-100,线程1、线程2交替打印
public class NumberTest {
    public static void main(String[] args) {
        Number number=new Number();
        Thread thread1=new Thread(number);
        Thread thread2=new Thread(number);

        thread1.setName("线程1");
        thread2.setName("线程2");

        thread1.start();
        thread2.start();
    }
}


class Number implements Runnable{
    private int number = 1;

    @Override
    public  void run() {
        while(true){
            synchronized (this){
                notify();  //唤醒一个被wait的线程
                if(number<=100){
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                    try {
                        wait();//阻塞
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                else break;
            }

        }
    }
}
  • wait和sleep方法的异同点
    相同点:一旦执行,都能使当前线程进入阻塞状态
    不同点:
    1>两个方法声明的位置不同,Thread类中声明的sleep方法,Object类中声明的wait方法
    2>调用的范围不同:sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
    3>关于是否释放同步监视器:如果两个方法都是用在同步代码块或同步方法中,sleep不会释放同步监视器,wait方法会释放同步监视器
  • 经典问题:生产者/消费者问题
/**
 * @Author: ssy
 * @Description:  生产者消费者模式  多线程 线程安全   线程通信
 * @Date: Created in 16:40 2020/11/13
 * @Modified By:
 */
public class ProductTest {

    public static void main(String[] args) {
        Product product=new Product();
        Producer producer=new Producer(product);
        Customer customer=new Customer(product);

        producer.setName("生产者");
        customer.setName("消费者");

        producer.start();
        customer.start();
    }
}


class Product{
    private int account=0;
    public synchronized void produceProduct(){
        if(account<20){
            account++;
            System.out.println(Thread.currentThread().getName()+"生产商品号:"+account);
            notify();
        }
        else {
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    public synchronized void saleProduct(){

        if(account>0){
            System.out.println(Thread.currentThread().getName()+"消费商品号:"+account);
            account--;
            notify();
        }
        else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer extends Thread{
    private Product product;
    public Producer(Product product){
        this.product=product;
    }
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            product.produceProduct();
        }
    }
}

class Customer extends Thread{
    private Product product;
    public Customer(Product product){
        this.product=product;
    }
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            product.saleProduct();
        }
    }
}

线程的死锁问题

  • 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁,出现死锁后,不会出现异常,不会出现提示,只是线程都处于阻塞状态,无法继续
  • 解决的方法:专门的算法、原则;尽量减少同步资源的定义;尽量避免嵌套同步

线程的创建和使用(续--后四种--JDK5.0新增线程创建方式)

3️⃣继承Callable接口
  • 与继承Runnable接口相比,Callable接口的更能更强大
    ①有返回值
    ②可以抛出异常(throws)
    ③支持泛型的返回值
    ④需要借助FutureTask类,比如获取返回结果
  • Future接口
    ①可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
    ②FutureTask是Future接口的唯一的实现类
    ③FutureTask同时实现了Runnable、Future接口,它既可以作为Runnable接口被线程执行,又可以作为Future接口得到Callable的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @Author: ssy
 * @Description:
 * @Date: Created in 9:30 2020/11/16
 * @Modified By:
 */
public class ThreadTest3 {

    public static void main(String[] args) {
        MyCallable myCallable=new MyCallable();
        FutureTask futureTask=new FutureTask(myCallable);


        Thread thread=new Thread(futureTask);
        thread.setName("当前线程");
        thread.start();

        //获取线程的返回值
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class MyCallable implements Callable{
    @Override
    public Object call() throws Exception {
        for(int i=0;i<100;i++){
            if(i%2==0)
                System.out.println(Thread.currentThread().getName()+": "+i);
        }
        return 100;//测试return值
    }
}
4️⃣使用线程池💢
  • 线程池适用于经常创建和销毁、使用量特别大的资源、比如并发情况下的线程,对性能影响很大。提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 好处:提高响应速度(减少了创建新线程的时间)、降低资源消耗(重复使用线程池中的线程,不需要每次都创建)、便于线程管理(corePoolSize、maximumPoolSize、keepAliveTime.......几个属性的搭配设置很重要,但我还只是学基础,就还没有看)
  • 线程池相关API(ExecutorService和Executors)
  • ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
    void execute(Runnable command); 执行任务/命令,没有返回值,一般用来执行Runnable
    <T> Future<T> submit(Callable<T> task); 执行任务,有返回值,一般用来执行Callable
    void shutdown(); 关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    ①Executors.newCachedThreadPool() 创建一个可根据需要创建新线程的线程池
    ②Executors.newFixedThreadPool(n) 创建一个可重用固定线程数的线程池
    ③Executors.newSingleThreadPool() 创建一个只有一个线程的线程池
    ④Executors.newScheduledThreadPool(n) 创建一个线程池,它可以安排在给定延迟后运行命令或者定期地执行
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Author: ssy
 * @Description:
 * @Date: Created in 9:58 2020/11/16
 * @Modified By:
 */
public class ThreadTest4 {
    public static void main(String[] args) {
        //工厂方法创建不同类型的线程池,利用多态性使用接口来承载
        ExecutorService poolExecutor= Executors.newFixedThreadPool(10);

        //需要对属性进行设置的时候,需要强转为相应类的对象,因为接口中定义的属性为常量,不能用接口来改变属性
        ThreadPoolExecutor pool=(ThreadPoolExecutor)poolExecutor;
        pool.setCorePoolSize(15);
        //pool.setKeepAliveTime(100,xx);

        //使用接口来调用函数,执行指定的线程的操作。需要提供实现Runnable或者Callable接口实现类的对象
        poolExecutor.execute(new MyRunnable());
        poolExecutor.submit(new MyCallable1());

        //不适用的时候要关闭连接池
        poolExecutor.shutdown();

    }
}


class MyRunnable implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            if(i%2==0)
                System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}


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

推荐阅读更多精彩内容