1. 同步(Synchonous)和异步(Asynchronous)
同步和异步是用来形容一次方法调用的:
- 同步方法开始后,调用者必须等到方法结束才能进行后续的行为;
- 异步方法开始后,方法调用立即返回,调用者继续后续的行为,异步调用的方法完成后,再通知调用者。
2. 并发(Concurrency)和并行(Parallelism)
并发和并行指的是两个或者多个任务一起执行:
- 并发指的是短时间内多个任务交替执行
- 并行指的是多个任务同时执行
3.临界区
临界区表示被多个线程使用的公共资源,但是每一次只能有一个线程使用它。
比如打印机资源。
4.阻塞(Blocking)和非阻塞(Non-Blocking)
阻塞和非阻塞用来形容多线程之间的相互影响:
- 如果一个线程占用了临界区资源,其他所有需要这个资源的线程就需要在这个临界区中等待,这就导致了这些线程的挂起,这种情况就是阻塞。
- 如果没有一个线程能够妨碍其他线程的执行,这种情况就是非阻塞。
5.死锁(Deadlock),饥饿(Starvation)和活锁(Livelock)
死锁,饥饿,活锁,属于多线程情况下的线程活跃性问题:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。饥饿指的是某一个或者多个线程无法获得说需要的资源,导致一直无法执行。
可能的情况包括线程优先级过低,某线程长时间占用关键资源等。
与死锁相比,饥饿是可能在未来一段时间内解决的。活锁指的是资源不断在两个或者多个线程中跳动,没有一个线程可以同时拿到所有的资源而正常执行。
6.并发级别
由于临界区(资源)的存在,多线程之间的并发是受到控制的,并发的级别被分为:阻塞,无饥饿,无障碍,无锁,无等待五种:
6.1 阻塞(Blocking)
一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。
例如在使用了synchronized关键字,或者重入锁,得到的就是阻塞的线程。它们会在执行后续代码之前,得到临界区的锁,如果得不到,线程会挂起等待,直到占有了说需要的资源。
这是一种悲观的策略,认为两个线程之间很有可能发生冲突,以保护共享数据为第一优先级。
6.2 无饥饿(Starvation-Free)
线程之间如果有优先级,线程调度的时候就会优先满足高优先级的线程,即,对于同一个资源的分配,是不公平的。
对于非公平的锁来说,系统优先满足高优先级的线程,就可能会导致低优先级的线程产生饥饿;
如果锁是公平的,满足先来后到,那么就没有饥饿产生,所有的线程都有机会执行。
6.3无障碍(Obstruction-Free)
两个线程如果是无障碍的执行,那么它们不会因为临界区(资源)的问题导致一方被挂起。
这是一种最弱的非阻塞调度。一旦发生数据竞争,需要进行数据回滚。
这是一种乐观的策略,认为系统之间发生冲突的可能性不大,以读取为第一优先级,产生冲突了再进行回滚。
无障碍可以以来一个“一致性标记”来实现:线程修改数据之前,对“一致性标记”进行修改,表明数据不再安全。其他线程读取后发现一致性标记不同,就知道资源的访问产生了冲突,需要进行回滚。
6.4 无锁(Lock-Free)
无锁的情况下,所有下次你很都能尝试对临界区进行访问,需要保证必然有一个线程能够在有限步内完成操作,离开临界区。其并行都是无障碍的。
6.5 无等待(Wait-Free)
无等待的情况下,所有的操作都必须在有限步内完成。
可以分为“有界无等待”和“线程无关的无等待”,区别在于循环次数的限制不同。
7 原子性(Atomicity),可见性(Visibillity),有序性(Ordering),Happen-Before原则
- 原子性是指一个操作是不可中断的。
- 可见性是指当一个线程修改了某一个共享变量的值,其他线程能否立即知道这个修改。
- 有序性是指线程A的指令执行顺序在线程B看来是没有保证的。不过对于一个线程来说,它看到的指令执行顺序是一致的。
有序性产生的原因是程序执行的时候,可能会进行指令重排。 - Happen-Before原则是指令重排中不可违背的原则:
- 程序顺序原则:一个线程内保证语义的串行性
- volatile原则:volatile变量的写,先发生于读,这样保证了volatile变量的可见性
- 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
- 传递性:A先于B,B先于C,则A必然先于C
*线程的start()方法优先于它的每一个动作
- 线程的所有操作优先于线程的终结(Thread.join())
- 线程的中断(interrupt())先于被中断线程的代码
- 对象的构造函数执行,结束先于finalize()方法