Java并发编程基础面试题


1、并行与并发的区别

1)并行指多个事件在同一个时刻发生;并发指在某时刻只有一个事件在发生,某个时间段内由于 CPU 交替执行,可以发生多个事件。
2)并行没有对 CPU 资源的抢占;并发执行的线程需要对CPU 资源进行抢占。
3)并行执行的线程之间不存在切换;并发操作系统会根据任务调度系统给线程分配线程的 CPU 执行时间,线程的执行会进行切换。

2、什么是并发编程?

并发:
1)在程序设计的角度,希望通过某些机制让计算机可以在一个时间段内,执行多个任务。
2)一个或多个物理 CPU 在多个程序之间多路复用,提高对计算机资源的利用率。
3)任务数多余 CPU 的核数,通过操作系统的任务调度算法,实现多个任务一起执行。
4)有多个线程在执行,计算机只有一个 CPU,不可能真正同时运行多个线程,操作系统只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。

并发编程:用编程语言编写让计算机可以在一个时间段内执行多个任务的程序。

3、为什么要用并发编程?

1)"摩尔定律" 失效,硬件的单元计算能力提升受限;硬件上提高了 CPU 的核数和个数。并发编程可以提升 CPU 的计算能力的利用率;
2)提升程序的性能,如:响应时间、吞吐量、计算机资源使用率等;
3)并发程序可以更好地处理复杂业务,对复杂业务进行多任务拆分,简化任务调度,同步执行任务。

4、并发编程的缺点?

1)Java 中的线程对应是操作系统级别的线程,线程数量控制不好,频繁的创建、销毁线程和线程间的切换,比较消耗内存和时间。
2)容易带来线程安全问题。如线程的可见性、有序性、原子性问题,会导致程序出现的结果与预期结果不一致。
3)多线程容易造成死锁、活锁、线程饥饿等问题。此类问题往往只能通过手动停止线程、甚至是进程才能解决,影响严重。
4)对编程人员的技术要求较高,编写出正确的并发程序并不容易。
5)并发程序易出问题,且难调试和排查;问题常常诡异地出现,又诡异地消失。

5、导致并发程序出问题的根本原因是什么?

CPU、内存、IO 设备的读写速度差异巨大,表现为 CPU 的速度 > 内存的速度 > IO 设备的速度。程序的性能瓶颈在于速度最慢的 IO 设备的读写,也就是说当涉及到 IO 设备的读写,再怎么提升 CPU 和内存的速度也是起不到提升性能的作用。
为了更好地利用 CPU 的高性能:
1)计算机体系结构,给 CPU 增加了缓存,均衡 CPU 和内存的速度差异;
2)操作系统,增加了进程与线程,分时复用 CPU,均衡 CPU 和 IO 设备的速度差异;
3)编译器,增加了指令执行重排序,更好地利用缓存,提高程序的执行速度。

基于以上优化,给并发编程带来了三大问题。

1、 CPU 缓存,在多核 CPU 的情况下,带来了可见性问题
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到修改后的值;
2、操作系统对当前执行线程的切换,带来了原子性问题
原子性:一个或多个指令在 CPU 执行的过程中不被中断的特性;
3、编译器指令重排优化,带来了有序性问题
有序性:程序按照代码执行的先后顺序。

6、同步和异步的区别?

同步和异步关注的是消息通信机制。

同步:就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。

异步:当一个异步调用发出后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

举例子的话:去银行办理业务,可能会有两种方式:
1、客户排队等候;
2、每个人都有一个号码牌,然后机器叫号让客户办理业务。
第一种就是同步,(排队等候)就是同步等待消息通知,也就是客户要一直在等待银行办理业务情况。
第二种:(等待机器叫号通知)就是异步等待消息通知。在异步消息处理中,等待消息通知者往往注册一个回调机制,在所等待的事件被触发时由触发机制(机器叫号)通过某种机制找到等待该事件的人。

7、阻塞与非阻塞的区别?

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

还是上面的例子,去银行办理业务,如果是阻塞式调用的话,你会把自己“挂起”,一直等待轮到自己,但是如果是非阻塞式调用的话,你不用等待是否轮到自己,你可以先干自己的事情,等到机器叫号叫到你就可以了。

8、Java程序中如何保证多线程的运行安全?

线程的安全性问题体现在:
1)原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
2)可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
3)有序性:程序执行的顺序按照代码的先后顺序执行

导致原因:
1)缓存导致的可见性问题
2)线程切换带来的原子性问题
3)编译优化带来的有序性问题

解决办法:
1)JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
2)synchronized、volatile、LOCK,可以解决可见性问题
3)Happens-Before 规则可以解决有序性问题。

9、什么是守护线程?

Java线程分为用户线程和守护线程。
1)守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序,意思就是只要有一个非守护线程仍在工作,那么所有的守护线程就会一直工作;
2)Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。

注意:
1)setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行守护线程创建的线程也是守护线程;
2)守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题。

10、创建线程的方式有哪几种?

1)继承Thread类,重写run方法

public class TestExtThread {
    public static void main(String[] args) {
        new ThreadExt().start();
    }

}

class ThreadExt extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <3; i++) {
            System.out.println("thread t > " + i);
        }
    }
    
}

2)实现Runnable接口,重写run()方法

public class TestImplRunnable {

    public static void main(String[] args) {
        new Thread(new RunnableImpl()).start();
    }
}
class RunnableImpl implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i <3; i++) {
            System.out.println("thread t > " + i);
        }
    }
    
}

3)实现Callable接口,使用FutureTask类创建线程(可以实现有返回值的线程创建,并且可以抛出异常)

public class TestCallable {

    public static void main(String[] args) {

        CallableDemo cd = new CallableDemo();

        //使用Callable方式创建线程,需要FutureTask类的支持
        //用于接收运算结果,可以使用泛型指定返回值的类型
        FutureTask<Integer> result = new FutureTask<>(cd);

        new Thread(result).start();

        int sum = 0;

        // 接收运算结果
        // 只有当该线程执行完毕后才会获取到运算结果,等同于闭锁的效果
        try {
            sum = result.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("sum is " + sum);

    }
}

class CallableDemo implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        // 计算1-100的和
        int sum = 0;

        for (int i = 1; i <= 100; i++)
            sum += i;

        return sum;
    }

4)使用线程池创建线程

public class TestCreateThreadByThreadPool {

    public static void main(String[] args) {
        // 使用工具类 Executors 创建单线程线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        //提交执行任务
        singleThreadExecutor.submit(() -> {System.out.println("单线程线程池执行任务");});
        //关闭线程池
        singleThreadExecutor.shutdown();
    }
}
11、什么是线程池?

线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务,避免了创建和销毁线程的昂贵开销,因为 Java 中创建一个线程,需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。而使用线程池就能起到对线程重复利用的效果,节省资源开销,使得性能大大提升。

12、线程池有哪几种类型?

1)newFixedThreadPool(固定线程数的线程池)

作用:创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

特征:
(1)线程池中的线程处于一定的量,可以很好的控制线程的并发量
(2)线程可以重复被使用,在显示关闭之前,都将一直存在
(3)超出一定量的线程被提交时候需在队列中等待

2)newCachedThreadPool(可缓存线程池)

作用:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

特征:
(1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
(2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
(3)当线程池中,没有可用线程,会重新创建一个线程

3)newSingleThreadExecutor(单线程的线程池)

作用:创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中等待执行。

4)newScheduleThreadPool(延时线程池)

作用: 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者延迟执行线程活动

5)newSingleThreadScheduledExecutor(单线程的延时线程池)

作用: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)可定时或者延迟执行线程活动

13、线程池的四种拒绝策略是什么?

线程池中,有三个重要的参数,决定影响了拒绝策略:corePoolSize - 核心线程数,也即最小的线程数,workQueue - 阻塞队列 。maximumPoolSize - 最大线程数。当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。

总结起来,也就是一句话,当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略。

1)CallerRunsPolicy -- 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大;

2)AbortPolicy -- 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行;

3)DiscardPolicy -- 直接丢弃,其他啥都没有;

4)DiscardOldestPolicy -- 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入。

14、synchronized关键字的作用是什么?

Java 中关键字synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。

作用:
1)确保线程互斥地访问同步代码
2)保证共享变量的修改能够及时可见
3)有效解决重排序问题

用法:
1)修饰普通方法
2)修饰静态方法
3)指定对象,修饰代码块

特点:
1)阻塞未获取到锁、竞争同一个对象锁的线程
2)获取锁无法设置超时
3)无法实现公平锁
4)控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll()
5)锁的功能是 JVM 层面实现的
6)在加锁代码块执行完或者出现异常,自动释放锁

原理:
1)同步代码块是通过 monitorenter 和 monitorexit 指令获取线程的执行权
2)同步方法通过加 ACC_SYNCHRONIZED 标识实现线程的执行权的控制

15、volatile关键字的作用是什么?

Java 中 volatile 关键字是一个类型修饰符。JDK 1.5 之后,对其语义进行了增强。
1)保证了不同线程对共享变量进行操作时的可见性,即一个线程修改了共享变量的值,共享变量修改后的值对其他线程立即可见;
2)通过禁止编译器、CPU 指令重排序和部分 happens-before 规则,解决有序性问题;

volatile 可见性的实现:
1)在生成汇编代码指令时会在 volatile 修饰的共享变量进行写操作的时候会多出 Lock 前缀的指令
2)Lock 前缀的指令会引起 CPU 缓存写回内存
3)一个 CPU 的缓存回写到内存会导致其他 CPU 缓存了该内存地址的数据无效
4)volatile 变量通过缓存一致性协议保证每个线程获得最新值
5)缓存一致性协议保证每个 CPU 通过嗅探在总线上传播的数据来检查自己缓存的值是不是修改
6)当 CPU 发现自己缓存行对应的内存地址被修改,会将当前 CPU 的缓存行设置成无效状态,重新从内存中把数据读到 CPU 缓存。

16、Java中的锁是什么?

在并发编程中,经常会遇到多个线程访问同一个共享变量,当同时对共享变量进行读写操作时,就会产生数据不一致的情况。

为了解决这个问题
1)JDK 1.5 之前,使用 synchronized 关键字,拿到 Java 对象的锁,保护锁定的代码块。JVM 保证同一时刻只有一个线程可以拿到这个 Java 对象的锁,执行对应的代码块。
2)JDK 1.5 开始,引入了并发工具包 java.util.concurrent.locks.Lock,让锁的功能更加丰富。

常见的锁
1)synchronized 关键字锁定代码库
2)可重入锁 java.util.concurrent.lock.ReentrantLock
3)可重复读写锁 java.util.concurrent.lock.ReentrantReadWriteLock

17、Java 中不同维度的锁分类

可重入锁
1)指在同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。JDK 中基本都是可重入锁,避免死锁的发生。

公平锁 / 非公平锁
1)公平锁,指多个线程按照申请锁的顺序来获取锁。如 java.util.concurrent.lock.ReentrantLock.FairSync
2)非公平锁,指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程先获得锁。如 synchronized、java.util.concurrent.lock.ReentrantLock.NonfairSync

独享锁 / 共享锁
1)独享锁,指锁一次只能被一个线程所持有。synchronized、java.util.concurrent.locks.ReentrantLock 都是独享锁
2)共享锁,指锁可被多个线程所持有。ReadWriteLock 返回的 ReadLock 就是共享锁

悲观锁 / 乐观锁
1)悲观锁,一律会对代码块进行加锁,如 synchronized、java.util.concurrent.locks.ReentrantLock
2)乐观锁,默认不会进行并发修改,通常采用 CAS 算法不断尝试更新
3)悲观锁适合写操作较多的场景,乐观锁适合读操作较多的场景

粗粒度锁 / 细粒度锁
1)粗粒度锁,就是把执行的代码块都锁定
2)细粒度锁,就是锁住尽可能小的代码块,java.util.concurrent.ConcurrentHashMap 中的分段锁就是一种细粒度锁
3)粗粒度锁和细粒度锁是相对的,没有什么标准

偏向锁 / 轻量级锁 / 重量级锁
1)JDK 1.5 之后新增锁的升级机制,提升性能。
2)通过 synchronized 加锁后,一段同步代码一直被同一个线程所访问,那么该线程获取的就是偏向锁
3)偏向锁被一个其他线程访问时,Java 对象的偏向锁就会升级为轻量级锁
4)再有其他线程会以自旋的形式尝试获取锁,不会阻塞,自旋一定次数仍然未获取到锁,就会膨胀为重量级锁

自旋锁
1)自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样做的好处是减少线程上下文切换的消耗,缺点是循环占有、浪费 CPU 资源。

18、synchronized 和 java.util.concurrent.lock.Lock 之间的区别

1)实现层面不一样。synchronized 是 Java 关键字,JVM层面 实现加锁和释放锁;Lock 是一个接口,在代码层面实现加锁和释放锁
2)是否自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要在 finally {} 代码块显式地中释放锁
3)是否一直等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
4)获取锁成功是否可知。synchronized 无法得知是否获取锁成功;Lock 可以通过 tryLock 获得加锁是否成功
5)功能复杂性。synchronized 加锁可重入、不可中断、非公平;Lock 可重入、可判断、可公平和不公平、细分读写锁提高效率。

19、说说对于sychronized同步锁的理解

1)每个 Java 对象都有一个内置锁
2)线程运行到非静态的 synchronized 同步方法上时,自动获得实例对象的锁
3)持有对象锁的线程才能运行 synchronized 同步方法或代码块时
4)一个对象只有一个锁
5)一个线程获得该锁,其他线程就无法获得锁,直到第一个线程释放锁。任何其他线程都不能进入该对象上的 synchronized 方法或代码块,直到该锁被释放。
6)释放锁是指持锁线程退出了 synchronized 同步方法或代码块
7)类可以同时拥有同步和非同步方法
8)只有同步方法,没有同步变量和类
9)在加锁时,要明确需要加锁的对象
10)线程可以获得多个锁
11)同步应该尽量缩小范围

20、sleep()和wait()有什么区别?

1)sleep() 是 Thread 类的静态本地方法;wait() 是Object类的成员本地方法;
2)sleep() 方法可以在任何地方使用;wait() 方法则只能在同步方法或同步代码块中使用,否则抛出异常Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
3)sleep() 会休眠当前线程指定时间,释放 CPU 资源,不释放对象锁,休眠时间到,会自动苏醒继续执行;wait() 方法放弃持有的对象锁,进入等待队列,当该对象被调用 notify() / notifyAll() 方法后才有机会竞争获取对象锁,进入运行状态;
4)JDK1.8 sleep() 、wait() 均需要捕获 InterruptedException 异常。

21、notify()和notifyAll()有什么区别?

先解释两个概念。
1)等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。
2)锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待

区别:
notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。

22、什么是死锁?

线程死锁是指由于两个或者多个线程互相持有所需要的资源,导致这些线程一直处于等待其他线程释放资源的状态,无法前往执行,如果线程都不主动释放所占有的资源,将产生死锁。
当线程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

产生原因:
1)持有系统不可剥夺资源,去竞争其他已被占用的系统不可剥夺资源,形成程序僵死的竞争关系。
2)持有资源的锁,去竞争锁已被占用的其他资源,形成程序僵死的争关系。
3)信号量使用不当。

23、乐观锁和悲观锁的区别?

悲观锁(Pessimistic Lock):线程每次在处理共享数据时都会上锁,其他线程想处理数据就会阻塞直到获得锁。

乐观锁(Optimistic Lock):线程每次在处理共享数据时都不会上锁,在更新时会通过数据的版本号等机制判断其他线程有没有更新数据。乐观锁适合读多写少的应用场景

两种锁各有优缺点:
1)乐观锁适用于读多写少的场景,可以省去频繁加锁、释放锁的开销,提高吞吐量
2)在写比较多的场景下,乐观锁会因为版本不一致,不断重试更新,产生大量自旋,消耗 CPU,影响性能。这种情况下,适合悲观锁。

24、Java中实现线程通讯方式有哪些?

1)对象的 wait(long timeout)、wait(long timeout, int nanos)、wait() 方法,组合对象的 notify()、notifyAll()
2)显示锁:Lock.newCondition()、Condition await 系列方法、Condition signal()、signalAll()
3)信号量:Semaphore acquire 系列方法、release()系列方法

25、synchronized和volatile的区别是什么?

作用:
1)synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
2)volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。

区别:
1)synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
2)synchronized 可以保证线程间的有序性(猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,无法保证原子性。
3)synchronized 线程阻塞,volatile 线程不阻塞。

26、synchronized和ReentrantLock区别是什么?

1)synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
2)synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
3)synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
4)synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
5)synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
6)synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放。

27、Thread类中的start和run方法的区别?

1)启动一个线程需要调用Thread对象的start()方法;
2)调用线程的start()后,线程处于可运行状态,此时它可由JVM调度并执行,这并不意味着线程就会立即运行;
3)run()方法是线程运行时由JVM回调的方法,无需手动写代码调用;
4)直接调用线程的run()方法,相当于调用线程里继续调用方法,并未启动一个新的线程。

28、Java内存模型是什么?

Java内存模型即Java Memory Model(JMM)

JMM并不真实存在,而只是一个抽象的概念。JMM是和多线程相关的,更准确来说,JMM描述了一组规则或规范,这个规范定义了一个线程对共享变量的写入时对另一个线程是可见的。

Java的多线程之间是通过共享内存进行通信的,而由于采用共享内存进行通信,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而JMM就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、synchronized等关键字。

在JMM中,我们把多个线程间通信的共享内存称之为主内存,而在并发编程中多个线程都维护了一个自己的本地内存(这是个抽象概念),其中保存的数据是主内存中的数据拷贝。而JMM主要是控制本地内存和主内存之间的数据交互的。


image.png

当图中线程A与线程B要进行数据交互时,将要经历:

1)线程A把本地内存B中更新过的共享变量刷新到主内存中去。

2)线程B到主内存中去读取线程A刷新过的共享变量,然后copy一份到本地内存B 中去。

未完待续

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

推荐阅读更多精彩内容