一、基本概念
1.并发和并行
- 并发:多个程序、交替执行
- 并行:多个程序、同时执行
2.CPU、进程、线程
- 一个操作系统有多个CPU;一个CPU有多个进程;一个进程有多个线程。
- 各个CPU之间共享操作系统资源;各个进程之间共享CPU资源;各个线程之间共享进程资源。
- 所谓操作系统 / CPU的任务调度,实际上的调度对象是线程,而进程只是给线程提供了资源。
CPU:并发运行多个任务(指 进程、线程、中断)。CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个任务。
CPU上下文 = CPU寄存器 + 程序计数器
【CPU上下文 具体分为 进程上下文 / 线程上下文 / 中断上下文】
进程:资源分配的单位。包括内核和用户空间的资源。由内核管理和切换。
线程:CPU调度的单位。包括独立的寄存器和栈。
二、线程的实现
- 用户线程:由用户空间程序管理和调度的线程,运行在用户空间(专门给应用程序使用)。
- 程序一般不会直接去使用内核线程,而是使用内核线程的一种高级接口——轻量级进程
- 内核线程:由操作系统内核管理和调度的线程,运行在内核空间(只有内核程序可以访问)。
用户线程创建和切换成本低,但不可以利用多核CPU。
内核态线程,创建和切换成本高,可以利用多核CPU(每个内核线程都是独立的调度单元。一个被阻塞,不影响其他)。
三、线程状态及转换(Java)
线程的状态,指的是Thread 类中threadStatus的值。该值映射后对应上表 各个线程状态。
private volatile int threadStatus = 0;
//threadStatus值 映射的枚举类
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
1.new、runnable、running、terminatated
- Thread t = new Thread() 创建对象,这个时候,线程t就处于NEW状态。但他还不是“线程”。
- t.start()后,会执行一个native方法创建内核线程。这时候才有一个真正的线程创建出来,并即刻开始运行。这个内核线程与线程t进行1:1的映射。这时候t具备运行能力,进入RUNNABLE状态。
- 处于RUNNABLE且未运行的线程,会进入一个就绪队列中,等待操作系统的调度。等线程t获得了 CPU 时间片后就处于RUNNING状态。
- 当一个线程执行完毕(或者调用已经不建议的 stop 方法),线程的状态就变为 TERMINATED。进入TERMINATED后,线程的状态不可逆,无法再复活。
引申:可以直接调用Thread类的run方法吗?
调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。
new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
2.blocked、waiting
- 触发条件不同
- 唤醒条件不同
- 中断响应不同
阻塞 获取一个对象的锁(monitor lock),但该锁已经被另一个线程持有。
由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己
不响应中断
(非java.util.concurrent库中的锁,即synchronized)
等待 等待另一个线程执行某些操作。这种状态下,线程将不会消耗CPU资源,不会参与锁的竞争
需要等待另一个线程显式地唤醒自己
可响应中断
(Object.wait()、Thread.join()以及等待Lock或Condition)
3.状态转换
sleep() 和 yield() 类似,都会让当前线程交出CPU权限,让CPU去执行其他的线程。也都不会释放锁,只是回到的状态不同。
join()实际是利用了wait(),只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(通过isAlive()来判断)。
四、创建线程的方式
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口与FutureTask
4.使用线程池(Executor框架)
引申:
严格来说,Java就只有一种方式可以创建线程,那就是通过new Thread().start()创建。
而所谓的Runnable、Callable……对象,这仅仅只是线程体,也就是提供给线程执行的任务,并不属于真正的Java线程,它们的执行,最终还是需要依赖于new Thread()。
五、参考
Java并发编程面试题 | 小林coding (xiaolincoding.com)