1 走进并行世界

须要知道的几个概念

同步(Synchronous)和异步(Asynchronous)

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

异步方法一旦开始,方法调用会当即返回,调用者就能够继续执行后续行为。而异步方法一般会在另一个线程“真实”地执行,当这个异步调用真实完成时,则会通知调用者。安全

并发(Concurrency)和并行(Parallelism)

并发偏重于多个任务交替执行,而多个任务之间有可能仍是串行的。并行是真实意义上的“同时执行”。多线程

临界区

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

阻塞(Blocking)和非阻塞(Non-Blocking)

阻塞和非阻塞一般用来形容多线程间的相互影响。好比一个线程占用了临界区资源,那么其余须要这个资源的线程就必须在这个临界区中等待。等待会致使线程挂起,这种状况就是阻塞。app

非阻塞的意思与之相反,它强调没有一个线程能够妨碍其余线程执行。全部线程都会尝试不断前向执行。异步

死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)

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

死锁:A、B、C、D四辆小车在这种状况下都没法继续进行,它们彼此之间都占用了其余车辆的车道,若是你们都不肯意释放本身的车道,那么这个状态将永远维持下去,谁都不可能经过。函数

饥饿:是指某一个或者多个线程由于种种缘由没法得到所须要的资源,致使一直没法执行。性能

活锁:若是两个或两个以上的线程都秉承着“谦让”的原则,主动将资源释放给他人使用,那么就会出现资源不断在两个线程中跳动,而没有一个线程能够同时拿到全部资源而正常执行。这种状况就是活锁。优化

并发级别

根据控制并发的策略,咱们能够把并发的级别进行分类,大体分为阻塞、无饥饿、无障碍、无锁、无等待几种。

阻塞(Blocking)

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

当咱们使用synchronized重入锁时,咱们获得的就是阻塞的线程。

不管是synchronized或是重入锁,都会试图在执行后续代码后,获得临界区的锁,若是得不到,线程就会被挂起。

无饥饿

若是线程间有优先级,那么线程调度优先知足高优先级的线程。对于非公平的锁来讲,系统容许高优先级的线程插队。这样有可能致使低优先级产生饥饿。

可是若是锁时公平的,知足先来后到,那么饥饿就不会发生。全部线程都有机会执行。

无障碍

无障碍是一种最弱的非阻塞调度。两个线程若是是无障碍的执行,那么他们不会由于临界区的问题致使一方被挂起。换言之,你们均可以进入临界区。

若你们一块儿修改共享数据,数据改坏了怎么办呢?对于无障碍的线程来讲,一旦检测到这种状况,它就会当即对本身所作的修改进行回滚,确保数据安全。

悲观策略与乐观策略:若是说阻塞的控制方式是悲观策略(认为两个线程发生冲突的可能性很大),那非阻塞的调度就是一种乐观策略(认为两个线程发生冲突的可能性很小,用,如遇冲突,用回滚的方式解决)。

这个策略可能出如今临界区发生冲突时,全部线程可能都会不断回滚本身的操做,而没有一个线程走出临界区。

实现无障碍能够用一个“一致性标记”来实现。

无锁(Lock-Free)

无锁的并行都是无障碍的。在无锁的状况下,全部线程都能尝试对临界区进行访问,但不一样的是,无锁的并发保证必然有一个线程可以在有限步内完成操做离开临界区

在无锁的调用中,一个典型特色是可能会包含一个无穷循环。线程会不断尝试修改共享变量,无锁的并行老是能保证一个线程胜出。至于临界区失败的线程,它们会不断重试,直到胜出。若是老是不成功,则可能出现相似饥饿的现象。

无等待

无锁只要求有一个线程能够在有限步内完成操做,而无等待则在无锁的基础上更进一步进行扩展。它要求全部线程都必须在有限步内完成,这样就不会引发饥饿问题。

有关并行的两个重要定律

为何要使用并行程序?有两个目的。第一, 为了得到更好的性能;第二,因为业务模型的须要,确实须要多个实体。咱们关注更多的是性能的问题。

Amdahl定律

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

加速比越高,说明优化效果越好。

回到java:JMM

JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来创建的。所以,咱们首先必须了解这些概念。

原子性(Atomicity)

原子性是指一个操做是不可中断的。即便是多个线程一块儿执行,一个操做一旦开始,就不会被其余线程干扰。

int是原子性的、long在32位系统来讲,不是原子性的。

可见性(Visibility)

可见性是指当一个线程修改了某一个共享变量的值,其余线程是否可以当即知道这个修改。这只能发生在并行中。

有序性(Ordering)

在并发时,程序的执行可能会出现乱序。给人直观的感受就是:写在前面的代码,会在后面执行。有序性问题的缘由是由于程序在执行时,可能进行指令重排,重排后的指令与原指令的顺序未必一致。

指令重排能够保证串行语义一致,可是没有义务保证多线程间的语义也一致。

为何要指令重排呢?彻底是为性能考虑的。具体的缘由能够翻原书。

哪些指令不能重排:Happen-Before规则

  • 程序顺序原则:一个线程内保证语义的串行性
  • volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
  • 锁规则:解锁必然发生在随后的加锁前
  • 传递性:A先于B,B先于C,那么A必然先于C
  • 线程的start()方法先于它的每个动做
  • 线程的全部操做先于它的每个动做
  • 线程的全部操做先于线程的终结(Thread.join)
  • 线程的中断(interrupt())先于被中断线程的代码
  • 对象的构造函数执行、结束先于finalize()方法
相关文章
相关标签/搜索