实战Java高并发程序设计笔记第一章
2019-10-22 本文已影响0人
MisterDo
相关术语
同步和异步
同步和异步用来形容一次方法调用。
- 同步:方法调用一旦开始,调用者必须等到方法调用结束后,才能继续后续的行为
- 异步:类似于一个消息传递,一旦开始,方法调用立即返回,调用者可以继续后续的操作,不会阻碍调用者的工作。返回的结果会以通知的形式告知调用者
并发和并行
- 并发:多个任务在同一时间段内交替执行
- 并行:多个任务真正的同时进行,针对于多个CPU
临界区:
- 公共资源/共享数据
- 可以被多个线程使用,但每次只能有一个线程使用它
- 在并行程序中,临界区资源是保护的对象
阻塞和非阻塞
- 阻塞:当一个线程占有临界区资源时,其他所有需要这个资源的线程必须等待,线程会挂起
- 非阻塞:与阻塞相反,没有一个线程会影响其他线程的执行
死锁、饥饿和活锁
都属于多线程的活跃问题
- 死锁:线程之间相互占有对方需要的资源,相互等待,僵持
- 饥饿:某个或多个线程因为种种原因无法获得想要的资源,一直得不到执行
- 活锁:占有资源的线程,相互谦让,主动释放资源,使得没有一个线程可以同时拿到所有资源而执行
并发级别
阻塞
线程在其他资源释放其需要的资源前无法执行,被挂起等待,用synchronized或重入锁实现
无饥饿
- 饥饿是怎么产生的?线程存在优先级的问题,优先级高的线程会插队到等待CPU执行的调度队列,那些优先级低的一直在队尾,得不到执行
- 不管线程的优先级,始终遵守先来先服务的原则,那么所有线程都会得到执行,此时就是无饥饿级别
无障碍
- 最弱的非阻塞调度
- 线程之间不会因为临界区资源的问题而被挂起,但是一旦检测到因为多个线程同时修改而出现问题,就会回滚,以确保数据安全
- 乐观策略,阻塞悲观策略
- 一致性标记:线程在操作之前,先读取并保存这个标记,在操作完成后,再次读取,检查这个标记是否被更改过,若两者一致,说明资源访问没有冲突;若不一致,说明资源可能在操作过程中与其他写线程冲突,需要重试操作。而任何对资源有修改操作的线程,在修改数据前,都需要更新这个一致性标记,表示数据不再安全。
无锁
- 所有的线程都能尝试对临界区进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区
- 无穷循环
无等待
- 在无锁的基础上进行扩展,要求所有的线程都必须在有限步内完成
- RCU:对数据的读不加控制,在写数据时,先得到原始数据的副本,接着只修改副本数据,修改完成后,在合适的时机回写数据
有关并行的两个重要定律
并行的目的:
- 获得更好的性能
- 业务模型需要多个执行实体
Amdahl定律
- 定义了串行系统并行化后的加速比的计算公式和理论上限
Gustafson定律
- 试图说明处理器个数、串行比例和加速比之间的关系
回到Java:JMM
JMM:Java的内存模型
- 一种保证多个线程间可以有效地、正确地协同工作的规则
- 围绕着多线程的原子性、可见性和有序性建立
原子性
- 一个操作不可中断
- 32位系统中long型数据的读和写都不是原子性
可见性
- 指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改
有序性
- 指令重排保证串行语义的一致性:在串行程序中,重排之后的结果与重排之前的结果一直
- 指令重排无法保证多线层间的语义也一致
- 为什么需要指令重排?:流水线机制让CPU高效的执行,但流水线的中断会导致性能损失,指令重排的目的是为了尽量少的中断流水线
哪些指令不能重排:Happen-Before规则
- 程序顺序原则:一个线程内保证语义的串行性
- volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
- 锁规则:解锁unlock必然发生在随后的加锁lock前
- 传递性:A先于B,B先于C,那么A必然先于C
- 线程的start()方法先于它的每一个动作
- 线程的所有操作先于线程的终结(Thread.join())
- 线程的中断(interrrupt())先于被中断线程的代码
- 对象的构造函数执行、结束先于finalize()方法