衡量一个服务性能的高低好坏,每秒事物处理数(TPS)是重要的指标之一,而 TPS 值与程序的并发能力又有非常密切的关系
目录
- 一、硬件的效率与一致性
- 二、Java 内存模型
- 三、Java 与线程
这里是看书笔记,之前文章也有相关介绍:Java JMM:内存模型
一、硬件的效率与一致性
- 内存模型:在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象
- 为了充分利用运算单元,处理器对输入的代码进行乱序执行优化,在计算后将乱序执行的结果重组
二、Java 内存模型
- Java 内存模型的主要目的是定义程序中各种变量的访问规则,Java 多线程内存模型跟 cpu 缓存模型类似,基于 cpu 缓存模型来建立,Java 线程内存模型是标准化的,屏蔽掉了底层不同计算机的区别
- 主内存与工作内存
- Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存
- 不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成
- 内存间交互操作
- Java 虚拟机必须对下面的操作都是原子的、不可再分的
- lock(锁定):把一个变量标识为一条线程独占的状态,作用在主内存
- unlock(解锁):把一个处于锁定状态的变量释放出来,作用在主内存
- read(读取):把一个变量的值从主内存传输到线程的工作内存中,作用在主内存
- load(载入):把 read 操作从主内存中得到的变量值放入工作内存的变量副本中,作用在工作内存
- use(使用):把工作内存中一个变量的值传递给执行引擎,作用在工作内存
- assign(赋值):把一个从执行引擎接收的值赋给工作内存的变量,作用在工作内存
- store(存储):把工作内存中一个变量的值传送到主内存,作用在工作内存
- write(写入):把 store 操作从工作内存中得到的变量值放入主内存的变量中,作用在主内存
- 主内存拷贝到工作内存,按顺序执行 read 和 load 操作,从工作内存同步回主内存,按顺序执行 store 和 write 操作
- Java 虚拟机必须对下面的操作都是原子的、不可再分的
- 对于 volatile 型变量的特殊规则
- volatile 可以说是 Java 虚拟机提供的最轻量级的同步机制
- 定义 volatile 两项特性
- 保证此变量对所有线程的可见性
- 可见性:一条线程改了变量的值,其他线程能立即得知
- 基于 volatile 变量的运算在并发下是线程安全的
- 仍然要加锁来保证原子性
- 遇到运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
变量不需要与其他的状态变量共同参与不变约束
- 遇到运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
- 禁止指令重排序优化
- 硬件架构上,允许将多条指令不按程序规定的顺序分开发送给各个相应的电路单元进行处理
- 保证此变量对所有线程的可见性
- volatile 变量读操作的性能消耗与普通变量几乎没有什么差别
- 针对 long 和 double 型变量的特殊规则
- 允许虚拟机将没有被 volatile 修饰的 64 位数据类型的 load、store、read 和 write 这四个操作的原子性,这就是 “long 和 double” 的非原子性协定
- 针对 double 类型,由于现代中央处理器一般都有专门的浮点运算器FPU,专门处理单、双精度的浮点数据,所以不会出现非原子性访问的问题
- 原子性、可见性与有序性
- 原子性
- 由 Java 内存模型来直接保证的原子性变量操作包括 read、load、assign、use、store 和 write 留个
- 可以认为基本数据类型的访问、读写都是具备原子性的
- 更大范围的原子性保证需要使用 lock 和 unlock 来操作
- synchronized 块之间的操作也具备原子性
- 由 Java 内存模型来直接保证的原子性变量操作包括 read、load、assign、use、store 和 write 留个
- 可见性
- 一个线程修改了变量,其他线程也能立刻得知
- volatile 特殊规则保证了新值能立即同步到主内存,每次使用前立即从主内存刷新
- synchronized 和 final 也能实现可见性
- 同步块对一个变量 unlock 操作之前,必须先把此变量同步回主内存
- final 初始化完成,构造器没有把“ this ”引用传递出去,那么在其他线程中就能看见 final 字段对值
- 有序性
- 线程内观察,所有操作都是有序的,在一个线程看另一个线程所有操作都是无序的
- volatile 和 synchronized 保存线程之间有序
- volatile 有禁止指令重排序的语义
- synchronized 一个变量在同一个时刻只允许一条线程对其进行 lock 操作
- 绝大部分并发控制操作都能使用 synchronized 来完成
- 原子性
- 先行发生原则
- Java 内存模型中所有的有序性都仅靠 volatile 和 synchronized 来完成
- 先行发生是 Java 内存模型中定义的两项操作之间的偏序关系
- A 先行发生于 B,A 产生的影响被 B 观察到
- 程序次序规则、管程锁定规则、volatile 变量规则、线程启动规则、线程终止规则、线程中断规则、对象终结规则、传递性
- 时间先后顺序与先行发生原则之间基本没有因果关系,所以我们衡量并发安全问题的时候不受时间顺序干扰,一切以先行发生原则为准
三、Java 与线程
- 线程的实现
- 线程是比进程更轻量级的调度执行单位,线程是 Java 里面进行处理器资源调度的最基本单位
- Thread 类所有关键方法都是 Native
- Native 方法往往就意味着这个方法没有使用或无法使用平台无关的手段来实现
- 内核线程(KLT)实现:直接由操作系统内核支持的线程,1:1 实现
- 程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口–轻量级进程(LWP)
- 轻量级进程局限性
- 基于内核线程实现,各种线程操作都需要进行系统调用
- 用户线程实现:1:N 实现
- 广义上:只要不是内核线程,都可以认为是用户线程的一种
- 狭义上:完全建立在用户空间的线程库上
- 混合实现:N:M 实现
- 内核线程与用户线程一起使用
- Java 线程的实现
- HotSpot:每一个 Java 线程都是直接映射到一个操作系统原生线程来实现
- Java 线程调度
- 协同式线程调度
- 线程的执行时间由线程本身来控制,要主动通知系统切换到另外一个线程上
- 好处:实现简单
- 坏处:线程执行时间不可控制,会由于一个线程导致整个进程甚至系统阻塞(一个线程有问题,不通知系统切换线程)
- 抢占式线程调度
- 每个线程使用时间由系统来分配执行时间,线程的执行时间是由系统控制,不会出现一个线程导致整个系统阻塞的问题
- Java 使用的线程调度方式就是抢占式调度
- 协同式线程调度
- 状态转化
- 新建(New):创建后尚未启动的线程
- 运行(Runnable):有可能正在执行,也可能在等待操作系统分配执行时间
- 无限期等待(Waiting):等待其他线程显式唤醒
- 限期等待(Timed Waiting):一定时间之后由系统自动唤醒
- 阻塞(Blocked):线程阻塞,等待获取到一个排他锁,这个事件将在另一个线程放弃这个锁的时候发生,程序等待进入同步区域的时候。
- 结束(Terminated):已终止线程的线程状态