同步方法调用一旦开始,调用者必须等到方法调用返回后,才继续执行后续行为。java
异步方法一旦开始,方法调用会当即返回,调用者就能够继续执行后续行为。而异步方法一般会在另一个线程“真实”地执行,当这个异步调用真实完成时,则会通知调用者。安全
并发偏重于多个任务交替执行,而多个任务之间有可能仍是串行的。并行是真实意义上的“同时执行”。多线程
临界区用来表示一种公共资源或者说是共享数据,能够被多个线程使用。可是每一次,只能有一个线程使用它,一旦临界区资源被占用,其余线程要想使用这个资源,就必须等待。并发
阻塞和非阻塞一般用来形容多线程间的相互影响。好比一个线程占用了临界区资源,那么其余须要这个资源的线程就必须在这个临界区中等待。等待会致使线程挂起,这种状况就是阻塞。app
非阻塞的意思与之相反,它强调没有一个线程能够妨碍其余线程执行。全部线程都会尝试不断前向执行。异步
死锁、饥饿和活锁都属于多线程的活跃性问题。async
死锁:A、B、C、D四辆小车在这种状况下都没法继续进行,它们彼此之间都占用了其余车辆的车道,若是你们都不肯意释放本身的车道,那么这个状态将永远维持下去,谁都不可能经过。函数
饥饿:是指某一个或者多个线程由于种种缘由没法得到所须要的资源,致使一直没法执行。性能
活锁:若是两个或两个以上的线程都秉承着“谦让”的原则,主动将资源释放给他人使用,那么就会出现资源不断在两个线程中跳动,而没有一个线程能够同时拿到全部资源而正常执行。这种状况就是活锁。优化
根据控制并发的策略,咱们能够把并发的级别进行分类,大体分为阻塞、无饥饿、无障碍、无锁、无等待几种。
一个线程是阻塞的,那么在其余线程释放资源以前,当前线程没法继续执行。
当咱们使用synchronized
或重入锁时,咱们获得的就是阻塞的线程。
不管是synchronized
或是重入锁,都会试图在执行后续代码后,获得临界区的锁,若是得不到,线程就会被挂起。
若是线程间有优先级,那么线程调度优先知足高优先级的线程。对于非公平的锁来讲,系统容许高优先级的线程插队。这样有可能致使低优先级产生饥饿。
可是若是锁时公平的,知足先来后到,那么饥饿就不会发生。全部线程都有机会执行。
无障碍是一种最弱的非阻塞调度。两个线程若是是无障碍的执行,那么他们不会由于临界区的问题致使一方被挂起。换言之,你们均可以进入临界区。
若你们一块儿修改共享数据,数据改坏了怎么办呢?对于无障碍的线程来讲,一旦检测到这种状况,它就会当即对本身所作的修改进行回滚,确保数据安全。
悲观策略与乐观策略:若是说阻塞的控制方式是悲观策略(认为两个线程发生冲突的可能性很大),那非阻塞的调度就是一种乐观策略(认为两个线程发生冲突的可能性很小,用,如遇冲突,用回滚的方式解决)。
这个策略可能出如今临界区发生冲突时,全部线程可能都会不断回滚本身的操做,而没有一个线程走出临界区。
实现无障碍能够用一个“一致性标记”来实现。
无锁的并行都是无障碍的。在无锁的状况下,全部线程都能尝试对临界区进行访问,但不一样的是,无锁的并发保证必然有一个线程可以在有限步内完成操做离开临界区。
在无锁的调用中,一个典型特色是可能会包含一个无穷循环。线程会不断尝试修改共享变量,无锁的并行老是能保证一个线程胜出。至于临界区失败的线程,它们会不断重试,直到胜出。若是老是不成功,则可能出现相似饥饿的现象。
无锁只要求有一个线程能够在有限步内完成操做,而无等待则在无锁的基础上更进一步进行扩展。它要求全部线程都必须在有限步内完成,这样就不会引发饥饿问题。
为何要使用并行程序?有两个目的。第一, 为了得到更好的性能;第二,因为业务模型的须要,确实须要多个实体。咱们关注更多的是性能的问题。
加速比定义:加速比 = 优化前系统耗时 / 优化后系统耗时
加速比越高,说明优化效果越好。
JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来创建的。所以,咱们首先必须了解这些概念。
原子性是指一个操做是不可中断的。即便是多个线程一块儿执行,一个操做一旦开始,就不会被其余线程干扰。
int
是原子性的、long
在32位系统来讲,不是原子性的。
可见性是指当一个线程修改了某一个共享变量的值,其余线程是否可以当即知道这个修改。这只能发生在并行中。
在并发时,程序的执行可能会出现乱序。给人直观的感受就是:写在前面的代码,会在后面执行。有序性问题的缘由是由于程序在执行时,可能进行指令重排,重排后的指令与原指令的顺序未必一致。
指令重排能够保证串行语义一致,可是没有义务保证多线程间的语义也一致。
为何要指令重排呢?彻底是为性能考虑的。具体的缘由能够翻原书。