《实战java高并发程序设计》第一章草记
目录
1.1何去何从的并行计算
- 摩尔定律的失效和服务器端需要承受海量的业务压力使得并行计算成为了必不可少的手段
- 在本质上理解所谓并行,就是需要在硬件工程师将“多个CPU内核塞进一个CPU”的硬件基础上同时使多个内核运行起来。
- 同时的运行固然提升了处理事务的效率,如何保证这些CPU有效并且满足任务需求的完成任务就是学习这项技术的根本目的。
1.2你必须知道的几个概念
- 同步:同步的方法在被调用开始,调用者就必须等待方法的任务完成后才能继续后续的操作。
- 异步:异步操作更像是在调用者在发送一条指令,这条指令只是传达调用者想做的事,但是调用者并不会等待这条任务完成。就好比客户需要你司给他定制开发一个软件,他只告诉你他要什么,但他不会傻等着你开发完而不去做其他的事情。
- 并发:实际上只使用了一个CPU内核,只是不同的任务交替使用这个内核而已。
- 并行:有多个CPU内核,不同线程实际上在不同的内核上运行。
- 临界区:是一种可以被多个线程使用的公共资源,但是这个资源一次只能一个线程使用。这通常是任务逻辑规定,并不是如CPU内核数量这种硬件条件。这是并发程序被保护的重点对象。
- 阻塞:和非阻塞共同形容多线程间的相互影响,描述的是并发的级别。阻塞表示当一个线程拿到了临界区进行使用使,其他所有的线程都必须等待这个线程释放临界区资源。
- 非阻塞:与阻塞相反,规定了没有任何一个线程可以妨碍其他线程使用临界资源,会不断的尝试前进。
- 死锁:与饥饿和活锁共同形容的是多线程的活跃度。书中小车占用其他小车的车道并都不释放的例子。这种情况最糟糕
- 饥饿:由于高优先级线程一直被调用,低优先级被调用的概率太低,导致低优先级线程无法完成任务。
- 活锁:由于线程过于“谦让”资源而导致都无法正常进行任务。
1.3并发级别
- 阻塞:前一节有提到。
- 无饥饿:既然饥饿是将线程分为了高低等级的,那无饥饿就是一个“线线平等”的,只按照先来后到的顺序分配资源,不存在优先级高就插队获取资源的情况就不存在饥饿了。
- 无障碍:是一种最弱的非阻塞调度。线程都可以对临界区无限制的使用,当出现由于临界区的冲突使用发生错误时,则线程回滚到使用临界区以前。这是建立在冲突乐观的条件下的策略。根据任务分析预估发生线程冲突的概率极小所采用的策略。
- 无锁:无锁的并行都是无障碍的。在无锁的调用中,保障一定有一个线程是可以成功修改临界区的,而其他争夺临界区资源的线程如果运气不好会出现饥饿的现象。
- 无等待:可以解决无锁的饥饿问题,在无锁只要求一个线程可以完成的基础上,要求所有的线程都能在有限的步骤中完成任务。典型的结构就是RCU(读、复制、修改),基本思想是,对读操作不加限制,然后修改其读数据的副本,在合适的时候再回写数据。
1.4有关并行的两个重要定律
- Amdahl定律:定义了串行系统并行化后的加速比的计算公式和理论上线。
加速比的定义: 加速比 = 优化前系统耗时/优化后系统耗时
F为只能串行执行的比例,n为处理器个数,由公式可知,加速比受处理器个数和串行化步骤的比例共同影响,只增加处理器数量的行为提高加速比是不够的,还需要修改程序的结构,降低串行化步骤。 - Gustafson定律:其加速比定义和Amdahl相同。公式如下。
Gustafson定律从可并行化占比出发,描述只要可以被并行化的代码足够多,那么理论上加速比就可以随着CPU数量的增加而增加。
1.5回到Java:JMM
Java 内存模型(JMM)是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为,Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。具体来说,这些方法包括 volatile、synchronized 和 final 三个关键字,以及八项 Happens-Before 规则
- 原子性:原子性指一个操作是不可中断的,是不被干扰执行的单位。
- 可见性:指当一个线程修改了某个临界区的值,其他线程是否知道这个修改。这是一个综合问题,缓存优化、硬件优化、指令重排和编辑器优化都有可能导致一个线程的修改不会立即被其他线程察觉。
- 有序性:因为指令重排只保证单线程指令是有序的,不保证多线程任务时指令的有序性而导致的问题,指令重排不一定发生。
- 指令重排:是一种为了提升CPU利用率的一种硬件优化策略。
- 不能重排的指令----Happen-Before规则※:
(1)程序顺序原则:一个线程内保证语义的串行性
(2)volatile规则:volatile先写后读,保证可见性
(3)锁规则:解锁发生于加锁后
(4)传递性:A先于B,B先于C,则A先于C
(5)线程启动规则:线程的start()方法先于这个方法的每一个动作
(6)线程结束规则:线程的所有动作都应该在他结束之前(Thread.join() )
(7)线程中断规则:被中断的代码在中断这个动作之后
(8)对象构造规则:对象的构造函数的执行和结束都在finallize()方法之前。