线程、JMM、线程池、并发编程3大特性

01 进程与线程的区别

进程:是正在执行的一段程序,一旦程序被载入内存准备执行,那就是一个进程。进程是表示资源分配的基本概念,又是调度运算的基本单位,是系统的并发执行单元。

线程:单个进程中执行的每个任务都是一个线程,线程是进程中执行运算的最小单位。

线程的意义:提高程序效率,充分发挥多和计算机的优势。

02 多线程创建

1,继承Thread类,实现run();

public static class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("我的一个线程");
    }
}
public static void main(String[] args) {
    MyThread t = new MyThread();
    t.start();
}

2,实现Runnable接口,将对象做为Thread的构造参数传入

public static class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("我的一个线程");
    }
}
public static void main(String[] args) {
    Thread t = new Thread(new MyRunnable());
    t.start();
}

还有比较灵活的写法:

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("匿名内部类写法");
    }
});
//JDK1.8版本以后
Thread t2 = new Thread(()->{
    System.out.println("兰姆达表达式");
});
t1.start();
t2.start();

3,实现Callable接口的call(),允许线程执行完以后获取返回结果

public static class MyCall implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "返回结果";
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    FutureTask<String> task = new FutureTask<String>(new MyCall());
    Thread thread = new Thread(task);
    thread.start();
    System.out.println(task.get());
}

03 用户线程与守护线程

当JVM运行起来以后,会有两种线程:用户线程与守护线程。

JVM关闭的条件:当用户线程运行完毕后,即使存在守护线程,它也会关闭。所以不能绝对的说:用户线程和守护线程都运行完毕了JVM才关闭。

在Java里有个很出名的守护线程就是:GC垃圾回收线程。

做一个小测试:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    Thread thread1 = new Thread(()->{
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1:"+i);
        }
    });
    //守护线程
    thread1.setDaemon(true);
    thread1.start();
    //用户线程
    Thread thread2 = new Thread(()->{
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2:"+i);
        }
    });
    thread2.start();
}

输出结果:

线程2:94
线程2:95
线程1:25
线程2:96
线程2:97
线程2:98
线程1:26
线程2:99
Process finished with exit code 0

上面的thread1.setDaemon(true);设置成了守护线程,当用户线程结束后,守护线程也停止运行了。

04 线程的优先级

CPU内核同一时刻只能执行一条线程,内核靠线程调度器来分配时间片来执行线程。所以线程需要抢夺时间片来执行,那么优先级其实就是设置抢夺的概率。

public static void main(String[] args){
    Thread thread1 = new Thread(()->{
        for (int i = 0; i < 100; i++) {
            System.out.println("线程1:"+i);
        }
    });
    Thread thread2 = new Thread(()->{
        for (int i = 0; i < 100; i++) {
            System.out.println("线程2:"+i);
        }
    });
    thread1.setPriority(10);
    thread2.setPriority(5);
    thread1.start();
    thread2.start();
}

输出结果:

线程1:97
线程1:98
线程1:99
线程2:0
线程2:1
线程2:2
线程2:3
线程2:4

可以看到线程1执行完毕后线程2才开始执行,说明线程1争夺时间片的概率大。

05 线程的生命周期

线程的生命周期

06 join方法

在线程执行的时候让别的线程调用join先插队,等别的线程执行完后再执行本线程。

public static void main(String[] args){
    Thread t1 = new Thread(()->{
        for (int i = 0; i < 3; i++) {
            System.out.println("t1:"+i);
        }
    });
    Thread t2 = new Thread(()->{
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 3; i++) {
            System.out.println("t2:"+i);
        }
    });
    Thread t3 = new Thread(()->{
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 3; i++) {
            System.out.println("t3:"+i);
        }
    });
    t1.start();
    t2.start();
    t3.start();
}

输出结果:

t1:0
t1:1
t1:2
t2:0
t2:1
t2:2
t3:0
t3:1
t3:2
Process finished with exit code 0

07 JMM内存模型

JMM(Java Memory Model)


JMM内存模型

线程是不能够直接修改主存内的数据的,而是先从主存中读取到自己的工作内存中创建副本,修改完成后写入到主内存,这就是JMM模型。

这也是为什么多线程并发访问修改数据的时候为什么出现安全问题。

08 并发编程的三大特性

原子性

一个操作或多个操作,要么全部执行并且执行过程不被打断,要么全部不执行(提供互斥访问,在同一时刻只有一个线程进行访问)

可以通过加锁的方式。

先看不加锁:

static int num = 100;
public static void main(String[] args){
    Runnable runnable = ()->{
        while (true){
            if(num>0){
                num--;
            }else{
                break;
            }
            System.out.println(num);
        }
    };
    new Thread(runnable).start();
    new Thread(runnable).start();
    new Thread(runnable).start();
}

输出了诡异的结果:

1
0
98
94
Process finished with exit code 0

如果加锁:

static int num = 100;
public static void main(String[] args){
    Object o = new Object();
    Runnable runnable = ()->{
        while (true){
            synchronized (o){
                if(num>0){
                    num--;
                }else{
                    break;
                }
                System.out.println(num);
            }
        }
    };
    new Thread(runnable).start();
    new Thread(runnable).start();
    new Thread(runnable).start();
}

输出结果:

5
4
3
2
1
0
Process finished with exit code 0

加锁需要传入一个对象,本程序中该对象是唯一的,如果直接写new Object()和不加锁效果一样。

可见性

public static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
    new Thread(()->{
        System.out.println("线程一号启动");
        while (!flag){};
        System.out.println("线程一号结束");
    }).start();
    Thread.sleep(1000);
    new Thread(()->{
        flag = true;
        System.out.println("把flag修改为了true");
    }).start();
}

显然对于一号线程而言,当二号线程修改了flag的值后,一号线程并没有及时获得flag,也就是说flag对于一号线程是不可见的。flag的值没有得到及时的更新。

有序性

JVM为了提高效率,会做出一些优化,对指令进行重排序(happens-before),在单线程的情况下没有问题,但是多线程编程需要考虑有序性问题。

volatile

1,保证可见性

2,屏蔽指令重排序

3,但是保证不了原子性

synchronized

JDK1.0开始提供的关键字,重量级的锁,能够保证某个代码块在被执行时,其他线程不访问执行。

synchronized必须使用对象做为锁,因为对象分为三部分:头部分里面有个锁的字段,synchronized就是利用该字段达到上锁的目的。

注意:假如有两个方法,要想实现调用fun1时fun2不能被访问,必须使用同一个对象加锁

public static Object o ;
public static  void fun1(){
    synchronized (o){}
}
public static void fun2(){
    synchronized (o){}
}

Monitor

JVM 是通过进入、退出对象监视器(Monitor)来实现对方法,同步块的同步。

使用synchronized加锁定的代码块,在被编译后会形成对象监视器的入口(monitorenter)和出口(monitorexit)


对象监视器

使用对象Object做为锁的时候,当有线程访问同步代码块的时候,监视器入口(monitorenter)会去检查Object是否上锁,没有上锁,则让该线程访问,同时给该对象上锁,此时若有其他线程访问该代码块的时候,监视器入口通过对比Object,发现上锁了,就会让其他线程处于阻塞状态,当第条线程执行完毕退出(monitorexir)以后,会给该对象解锁,那么被阻塞的线程就可以接着访问,并保证了原子性。

public class Demo {
    public static synchronized void get(){
        //是把Demo.class(当前类的字节码文件)做为对象加锁
    }
    public synchronized void to(){
        //是把this(当前this锁)做为对象枷锁
    }
    public static void main(String[] args) {
        Demo demo = new Demo();
        demo.to();
        Demo.get();
    }
}

对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程,即使获取CPU的执行权,也进不去。

优点:解决了线程安全问题

缺点:多个线程需要判断锁,较为消耗资源,抢锁的资源

09 J.U.C之Lock

Lock

public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    //Lock是代码实现的接口,而synchronized是JVM底层实现的
    new Thread(()->{
        lock.lock();
        try{
            //加锁代码块
        }finally {
            //必须手动释放
            lock.unlock();
        }
    }).start();
}

trylock

上锁部分不被访问的时候,去做别的事情,而不是像synchronized那样必须阻塞。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo {
    Lock lock = new ReentrantLock();
    public void insert(Thread thread){
        if(lock.tryLock()){
            //抢到锁了,就调用这里的方法
            try{
                System.out.println(thread.getName()+"抢到锁啦");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println(thread.getName()+"释放锁啦");
            }
        }else{
            //如果没有抢到锁,就调用这里的方法
            System.out.println(Thread.currentThread()+"没有抢到锁啦");
        }
    }
    public static void main(String[] args) {
        Demo d = new Demo();
        new Thread(()->{
            d.insert(Thread.currentThread());
        }).start();
        new Thread(()->{
            d.insert(Thread.currentThread());
        }).start();
    }
}

Lock与synchronized的区别

1)Lock是一个接口,而synchronized是一个关键字,是Java内置的实现,它可以直接修饰方法和代码块,而lock只能修饰代码块。

2)synchronized在发生异常的时候,会自动释放线程占有的锁,因此不会导致死锁现象,而Lock发生异常以后,如果没有unLock()释放锁,很可能造成死锁现象,因此在使用Lock的时候需要在finally中释放锁。

3)Lock可以让等待的线程响应中断,去干别的事情,而synchronized会让线程一直阻塞下去。

4)通过Lock的trylock()可以知道有没有成功获取锁,而synchronized不行。

5)Lock可以提高多线程进行读操作的效率(提供读写锁)。

从性能上来说,如果竞争资源很激烈,Lock的性能远远大于synchronized。

10 线程通信

wait与notify使用两个线程交替打印1-100,其中一条线程只打印奇数,另外一个线程只打印偶数。

public class Wait {
    static class Num{
        public int num = 1;//共享资源
    }
    static class J implements Runnable{
        public Num numObj;
        public J(Num n){
            this.numObj = n;
        }
        @Override
        public void run() {
            synchronized (numObj){
                while (numObj.num<=100){
                    if(numObj.num%2!=0){
                        System.out.println("奇数====>"+numObj.num);
                        numObj.num++;

                    }else {
                        try {
                            numObj.notify();
                            numObj.wait();//wait()要写在代码块里面,因为它的作用是释放锁,并且等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    static class O implements Runnable{
        public Num numObj;
        public O(Num n){
            this.numObj = n;
        }
        @Override
        public void run() {
            synchronized (numObj){
            while (numObj.num<=100) {
                if (numObj.num % 2 == 0) {
                    System.out.println("偶数====>" + numObj.num);
                    numObj.num++;
                } else {
                    try {
                        numObj.notify();
                        numObj.wait();//wait()要写在代码块里面,因为它的作用是释放锁,并且等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            }
        }
    }
    public static void main(String[] args) {
        Num numObj = new Num();
        new Thread(new J(numObj)).start();//奇数线程
        new Thread(new O(numObj)).start();//偶数线程
    }
}

wait()的作用是,释放当前锁,并使当前线程处于等待状态,所以需要写在同步代码块里,notify()是唤醒一个处于等待状态的线程,本题只有两条线程,如果有多条线程,那么可以使用notifyAll()。

这三个方法最终调用的都是JVM级的native方法。

11 什么是线程池

线程池是Java的并发框架,几乎所有的并发执行程序或者异步操作都可以使用线程池。

1)降低资源消耗。通过重复使用已经创建好的线程降低创建和销毁的消耗。

2)提高响应速度。当任务到达时,不需要等到线程创建后就可以立即执行。

3)提高现成的可管理性。使用线程池可以进行统一的分配、调优、监控。

12 线程池工作原理

Executor是最基本的接口,其子接口:ExecutorService是工作中常用的接口
其中一个很重要的实现类是ThreadPoolExecutor,通过它new出来
import java.util.concurrent.*;

public class Pool {
     static class MyRunnable implements Runnable{
        String info = "";
        public MyRunnable(String txt){
            this.info = txt;
        }
        @Override
        public void run() {
            System.out.println(this.info);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
//        int corePoolSize      核心线程数
//        int maximumPoolSize   最大线程数
//        long keepAliveTime    保持存活得时间
//        TimeUnit unit         时间单位
//        BlockingQueue<Runnable> workQueue     任务队列
//        RejectedExecutionHandler handler      饱和策略
        ExecutorService es = new ThreadPoolExecutor(
                2,//corePoolSize
                5,//maximumPoolSize
                10,//keepAliveTime
                TimeUnit.SECONDS,//unit
                new ArrayBlockingQueue<Runnable>(5),
                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 1; i <= 20; i++) {
            try{
                es.execute(new MyRunnable("执行第"+i+"条线程"));
            }catch (Throwable e){
                System.out.println("丢弃线程"+i);
            }
        }
        es.shutdown();
    }
}
线程池

四个流程:

1)判断核心线程数

2)判断任务队列

3)判断最大线程数(备胎线程)

4)执行饱和策略

存活时间参数:当最大线程数(备胎线程)执行完毕,并且没有新任务的前提下,只能存活(keepAliveTime unit )的时间,超过时间就会被释放掉。

13 三种常见队列

SynchronousQueue

一次性只能装一个任务,其他任务处于阻塞状态,同时一次性只能取出一个任务,如果没有任务可以取出,也处于阻塞状态

import java.util.concurrent.SynchronousQueue;

public class Queue {
    public static void main(String[] args) {
        SynchronousQueue<Integer> queue = new SynchronousQueue<>();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    queue.put(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("装入数据====>"+i);
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println("取出数据=====>"+queue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
//执行结果
取出数据=====>0
装入数据====>0
    隔了两秒
取出数据=====>1
装入数据====>1
    隔了两秒
取出数据=====>2
装入数据====>2‘
    隔了两秒
取出数据=====>3
装入数据====>3

LinkedBlockingQueue

瞬间装完所有的任务,然后慢慢取出

import java.util.concurrent.LinkedBlockingQueue;

public class Queue {
    public static void main(String[] args) {
        LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    queue.put(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("装入数据====>"+i);
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println("取出数据=====>"+queue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
//执行结果
装入数据====>18
装入数据====>19
    瞬间装完20个数据
取出数据=====>0
    隔了两秒
取出数据=====>1
    隔了两秒
取出数据=====>2
    隔了两秒
取出数据=====>3

ArrayBlockingQueue

设置填装大小,比如4,那么一次性装入4个,其余的取出一个就装一个

import java.util.concurrent.ArrayBlockingQueue;

public class Queue {
    public static void main(String[] args) {
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(4);
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    queue.put(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("装入数据====>"+i);
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println("取出数据=====>"+queue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
//执行结果
装入数据====>0
装入数据====>1
装入数据====>2
装入数据====>3
    瞬间完成
取出数据=====>0
装入数据====>4
    隔了两秒
取出数据=====>1
装入数据====>5
    隔了两秒

14 饱和策略

CallerRunsPolicy
    不抛弃任务,调用线程池的线程,帮助执行任务
    比如上面的main方法调用的线程池,16号线程就会在main中执行
演示代码:
import java.util.concurrent.*;

public class Pool {
     static class MyRunnable implements Runnable{
        String info = "";
        public MyRunnable(String txt){
            this.info = txt;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+":"+this.info);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        ExecutorService es = new ThreadPoolExecutor(
                2,
                5,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(10),
                new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 1; i <= 20; i++) {
            try{
                es.execute(new MyRunnable("执行第"+i+"条线程"));
            }catch (Throwable e){
                System.out.println("丢弃线程"+i);
            }
        }
        es.shutdown();
    }
}
执行结果:
pool-1-thread-2:执行第2条线程
pool-1-thread-1:执行第1条线程
main:执行第16条线程
pool-1-thread-3:执行第13条线程
pool-1-thread-4:执行第14条线程
pool-1-thread-5:执行第15条线程
AbortPolicy(默认)
    当最大线程数满了以后,抛出异常,抛弃任务
DiscardPolicy
    连异常都不抛,直接把任务丢了
DiscardOldestPolicy
    连异常都不抛,直接把任务丢了

15 线程池工具类

工具类:Executors 快速创建线程池

缓存线程池

newCachedThreadPool

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Es {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            System.out.println("线程"+i);
            executorService.execute(()->{
                for (int j = 0; j < 5; j++) {
                    System.out.println(Thread.currentThread().getName()+":"+j);
                }
            });
        }
        executorService.shutdown();
    }
}

源码的实现:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

可以发现:没有核心线程数,扔进去多少线程,就会创建多少线程,好处是线程复用以及可以及时释放线程,弊端就是,当任务量极大的时候,他就创建一大堆线程。

定长线程池

newFixedThreadPool

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Es {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 10; i++) {
            System.out.println("线程"+i);
            executorService.execute(()->{
                for (int j = 0; j < 5; j++) {
                    System.out.println(Thread.currentThread().getName()+":"+j);
                }
            });
        }
        executorService.shutdown();
    }
}

源码的实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

单例线程池

newSingleThreadExecutor

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Es {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            System.out.println("线程"+i);
            executorService.execute(()->{
                for (int j = 0; j < 5; j++) {
                    System.out.println(Thread.currentThread().getName()+":"+j);
                }
            });
        }
        executorService.shutdown();
    }
}

可以发现单例线程池,会由一个线程完成所有的任务。

源码的实现:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

调度线程池

ScheduledExecutorService:newScheduledThreadPool

可以实现:延迟执行的线程池

public static void main(String[] args) {
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    //延迟执行的线程池
    scheduledExecutorService.schedule(()->{
        System.out.println("过去5秒种啦");
    },5, TimeUnit.SECONDS);//第二个参数是延迟时间,然后是时间单位
    scheduledExecutorService.shutdown();
}

可以实现:周期执行的线程池

public static void main(String[] args) {
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    //周期执行的线程池:5s之后开始执行任务,每隔2s重复执行一次
    scheduledExecutorService.scheduleAtFixedRate(()->{
        System.out.println("执行啦");
    },5,2, TimeUnit.SECONDS);//参数:延迟时间 间隔时间 时间单位
    //scheduledExecutorService.shutdown();这时候不要shutdown
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容