文章目录
1. 同步(Synchronous)和异步(Asynchronous)
同步和异步通常用来形容一次方法调用。
-
同步
- 调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。 异步
- 调用一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。
2. 并发(Concurrency)和并行(Parallelism)
-
并发
- 多个任务交替执行,而多个任务之间有可能还是串行的。这个过程只是交替的,一会儿运行任务A一会儿执行任务B,系统会不停地在两者之间切换。 并行
- 并行是多个任务同时执行。
3. 临界区
临界区用来表示一种公共资源或共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。
4. 阻塞(Blocking)和非阻塞(Non-Blocking)
阻塞和非阻塞通常用来形容多线程间的相互影响。
5. 死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)
5.1 死锁
死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。
死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。
死锁产生的4个必要条件
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
5.2 饥饿
指某一个或者多个线程因为种种原因无法获得所需的资源,导致一直无法执行。
饥饿有可能在未来一段时间内解决,即在未来某短时间后会继续执行后续功能。
情景
- 线程优先级太低
- 某一个线程一直占用资源不释放。
5.3 活锁
指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。
线程秉承着“谦让”的原则,主动将资源释放给他人使用,那么就会出现资源不断在两个线程中跳动,而没有一个线程可以同时拿到所有资源而正常执行。
6. 并发级别
并发级别分为阻塞、无饥饿、无障碍、无锁、无等待几种。
6.1 阻塞
一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。
6.2 无饥饿
在没有非公平锁的情况下,即为公平锁,所有线程会满足先来后到,那么饥饿就不会产生。
6.3 无障碍
无障碍是一种最弱的非阻塞调度。两个线程如果是无障碍的执行,那么他们不会因为临界区的问题导致一方被挂起。
如果检测同时修改共享数据后,会立即对自己所做的修改进行回滚,确保数据安全。
6.4 无锁
无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。
6.5 无等待
无等待在无锁的基础上更进一步,它要求所有的线程都必须在有限步内完成,就不会引起饥饿问题。
7. Amdahl定律
加速比定义:加速比= 优化前系统耗时 / 优化后系统耗时
a
=
1
F
+
1
n
(
1
−
F
)
{ a = \frac 1 {F+ \frac 1 n \left({1 - F}\right)}}
a=F+n1(1−F)1
F:为串行比例
n:为处理器个数
8. Java的内存模型(JMM)
8.1 原子性(Atomicity)
原子性是指一个操作是不可中断的。
多个线程对同一个数值进行修改,改变后的数值,只能是这几个线程中的数值,不会出现另外的数值。
8.2 可见性(Visibility)
可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。
由于存在cache和寄存器,线程会将数值拷贝到其中,造成一个线程修改原值,但是cache中或寄存器中的是指还是修改前的数值,造成可见性问题。
可见性问题:
- 缓存优化。
- 硬件优化。
- 指令重排。
- 编译器优化。
8.3 有序性(Ordering)
有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。
指令重排是有一个基本前提,保证串行语义的一致性。
注意:指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。
9. Happen-Before规则
以下原则是指令重排不可违背的:
- 程序顺序原则:一个线程内保证语义的串行性
- volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
- 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
- 传递性:A先于B,B先于C,那么A必然先于C
- 线程的start()方法先于它的每一个动作
- 线程的所有操作先于线程的终结(Thread.join())
- 线程的中断(interrupt())先于被中断线程的代码
- 对象的构造函数执行、结束先于finalize()方法