几个概念

同步和异步

同步和异步通常用来形容一次方法调用。

  • 同步

同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。

  • 异步

异步方法调用一旦开始,方法调用就会立即返回,调用者可以继续后续的操作,异步方法通常会在另一个线程中执行,整个过程不会阻碍调用者的工作。

并发和并行

并发和并行都可以表示两个或多个任务一起执行。

  • 并发

并发是多个任务交替执行,多个任务之间可能还是串行的

  • 并行

并行是多个任务真的同时执行

临界区

临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源就必须等待。

阻塞和非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响。

  • 阻塞

一个线程占用了临界区资源,其他所以需要这个资源的线程就必须在这个临界区中等待,等待导致线程挂起,这就是阻塞。

  • 非阻塞

非阻塞与阻塞相反,非阻塞强调没有一个线程可以妨碍其他线程执行。

死锁、饥饿和活锁

死锁、饥饿和活锁都属于多线程的活跃性问题。

  • 死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

  • 当前线程拥有其他线程需要的资源
  • 当前线程等待其他线程已拥有的资源
  • 都不放弃自己拥有的资源
    • 饥饿

饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。

  • 例如线程优先级低,一直抢不到资源执行
  • 某一个线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行
    • 活锁

活锁是指两个线程在竞争资源时,主动将资源释放给他人使用,导致资源不断地在两个线程间跳动,而没有一个线程可以同时拿到所有资源正常执行。

  • 活锁不会被阻塞,而是不停检测一个永远不可能为真的条件
  • 除去进程本身持有的资源外,活锁状态的进程会持续耗费宝贵的CPU时间

并发级别

阻塞的控制是悲观策略,非阻塞的调度是一种乐观策略。

阻塞

一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。

无饥饿

对于非公平锁来说,系统允许高优先级的线程插队,可能导致低优先级线程产生饥饿。但如果锁是公平的,按照先来后到的规则,那么饥饿就不会产生。

无障碍

无障碍是一种最弱的非阻塞调度。两个线程如果无障碍地执行,那么不会因为临界区的问题导致一方挂起。一种无障碍的实现方式就是依赖“一致性标记”:
线程在操作之前,先读取并保存这个标记,在操作完成后,再次读取,检查这个标记是否被更改过,如果两者是一致的,则说明资源访问没有冲突。如果不一致,则说明资源可能在操作过程中与其他写程序冲突,需要重试操作。任何对资源有修改操作的线程,在修改数据前,都需要更新这个一致性标记,表示数据不再安全。(CAS比较)

无锁

无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区进行访问,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。

无等待

无等待是在无锁的基础上更进一步扩展,它要求所有的线程都必须在有限步内完成。

并行定律

Amdahl定律

Amdahl定律定义了串行系统并行化后的加速比的计算公式和理论上限。

加速比 = 优化前系统耗时 / 优化后系统耗时

根据Amdahl定律使用多核CPU对系统进行优化,优化的效果取决于CPU的数量,以及系统中的串行化程序的比例。CPU数量越多,串行化比例越低,则优化效果越好。仅提高CPU数量而不降低程序的串行化比例,无法提高系统性能。

Gustafson定律

Gustafson定律与Amdahl定律类似,只是角度不同。

JMM—Java内存模型

原子性

原子性是指一个操作是不可中断的。

可见性

可见性是指当一个线程修改了某一个共享变量的值时,其他线程是否能够立即知道这个修改。

有序性

对于一个线程的执行,代码是从前往后依次执行的,但在并发时,程序的执行可能会出现乱序。有序性问题的原因是程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。

  • 指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。