一、进程与线程的区别
1. 进程
- 程序是由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存
- 在指令运行过程中还需要用到磁盘、网络等设备
- 进程就是用来加载指令、管理内存、管理IO的
- 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程
2. 线程
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行
- 一个进程之内可以分为一到多个线程
3. 二者区别
- 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
- 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
- 线程更轻量,线程上下文切换成本一般要比进程上下文切换低(上下文切换:指从一个线程切换到另一个线程)
二、并行与并发的区别
1. 单核CPU
- 单核CPU下线程实际是串行执行的
- 操作系统中有一个组件为任务调度器,其将CPU的时间片(Windows下时间片最小约为15毫秒)分给不同的程序使用,由于CPU在线程之间的切换非常快,人类感觉是同时运行的
- 总结:微观串行,宏观并行
- 一般会将这种线程轮流使用CPU的做法称为并发(Concurrent)
2. 多核CPU
- 每个核(Core)都可以调度运行线程,这时候线程可以是并行的
3. 二者区别
- 现在都是多核CPU,在多核CPU下
(1)并发(Concurrent):同一时间应对(dealing with)多件事情的能力,多个线程轮流使用一个或多个CPU
(2)并行(Parallel):同一时间动手(doing)多件事情的能力,4核CPU同时执行4个线程
三、线程创建的方式有哪些
1. 继承Thread类
2. 实现Runnable接口
3. 实现Callable接口
4.线程池创建线程【项目中推荐使用该方式】
四、Runnable和Callable接口有什么区别
- Runnable接口的run()方法没有返回值
- Callable接口的call()方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
五、线程包括哪些状态,状态之间是如何变化的
1. 线程的状态
- 可以参考JDK中Thread类中的枚举类State
2. 状态之间转换
- 创建线程对象是新建状态
- 调用了start()方法转变为可执行状态
- 线程获取到了CPU的执行权,执行结束是终止状态
- 在可执行状态的过程中,如果没有获取到CPU的执行权,可能会切换成其他状态
(1)如果没有获取到锁(Synchronized或Lock),则进入阻塞状态;获得锁再切换为可执行状态
(2)如果线程调用了wait()方法,则进入等待状态,其他线程调用notify()方法唤醒后可切换为可执行状态
(3)如果线程调用了sleep()方法,则进入计时等待状态,到时间后可切换为可执行状态
六、在Java中wait()和sleep()方法的不同
1. 共同点
- wait()/wait(long)和sleep(long)方法:效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
2. 不同点
- 方法归属不同
(1)wait()/wait(long)方法:Object类的成员方法,每个对象都有
(2)sleep(long)方法:Thread类的静态方法 - 醒来时机不同
(1)wait()/wait(long)方法可以被notify()/notifyAll()方法唤醒,wait()方法如果不被唤醒则一直等下去
(2)执行wait(long)和sleep(long)方法的线程都会在等待相应毫秒后醒来
(3)它们都可以被打断唤醒 - 锁特性不同(重点)
(1)wait()/wait(long)方法的调用必须先获取对象的锁,而sleep(long)方法无此限制
(2)wait()/wait(long)方法执行后会释放对象锁,允许其他线程获得该对象锁(释放锁,在睡觉)
(3)sleep(long)方法如果在Synchronized代码块中执行,并不会释放对象锁(抱着锁,在睡觉)
七、新建三个线程T1、T2、T3,如何保证它们按顺序执行
1. join()方法
- join()方法:等待线程运行结束
- t.join():阻塞调用此方法的线程,进入timed_waiting,知道线程t执行完成后,此线程再继续执行
八、notify()和notifyAll()方法有什么区别
- notify()方法:只随机唤醒一个wait线程
- notifyAll()方法:唤醒所有wait线程
九、线程的run()和start()方法有什么区别
- start()方法:用来启动线程,通过该线程调用run()方法执行run()方法中所定义的逻辑代码。start()方法只能被调用一次
- run()方法:封装了要被线程执行的代码,就是一个普通的方法,可以被调用多次
十、如何停止一个正在运行的线程
- 使用退出标志,使线程正常退出,也就是当run()方法完成后线程终止
- 使用stop()方法强制终止(不推荐,该方法已作废)
- 使用interrupt()方法中断线程
(1)打断阻塞的线程(sleep、wait、join),线程会抛出InterruptedException异常
(2)打断正常的线程,可以根据打断状态来标记是否退出线程