高效性与一致性
高效性
为了使读写速度尽可能接近处理器运算速度,引入了高速缓存来作为内存与处理器之间的缓冲。
流程:数据复制到缓存中,快速运算,将结果从缓存中同步回内存。
一致性
高效性引出了一致性问题。
在多路处理器系统中,每个处理器都有各自的高速缓存,又共享同一主内存,这种系统称为共享内存多核系统。
当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致
解决:协议
Java内存模型
定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节。
主内存与工作内存
Java内存模型规定了所有的变量都存储在主内存中。
每条线程有各自的工作内存,保存了被该线程使用的变量的主内存副本,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据。
内存间交互操作
一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回主内存
定义了以下8种操作,每一种操作都是原子的、不可再分的
lock, unlock, read, load, use, assign, store, write
主存->工作内存:read,load
工作内存->主存:store,write
voliatile
1.保证此变量对所有线程的可见性。
如果修改了该变量的值,其他线程立即得知。
2.禁止指令重排序优化,保证变量赋值操作的顺序与程序代码中的执行顺序一致。
注意:以下两种运算场景仍需加锁保证原子性:
1)运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
2)变量不需要与其他的状态变量共同参与不变约束。
性能
volatile变量读操作的性能消耗与普通变量几乎没有无差别,但是写操作则可能会慢,因为需要插入内存屏障指令
规则
load动作必须在use动作之前,且两个动作必须连续且一起出现。
assign动作必须在store动作之前,且两个动作必须连续且一起出现。
long和double类型变量
允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行。
允许虚拟机自行选择是否保证64位数据类型的load、store、read和write这四个操作的原子性。
原子性
实现:
直接保证的原子性变量操作:read、load、assign、use、store和write
更大范围:lock和unlock,synchronized
可见性
当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。
实现:volatile,synchronized和final
有序性
从本线程角度,所有操作有序;从另一线程角度,所有操作无序
实现:volatile和synchronized
先行发生原则
判断数据是否存在竞争,线程是否安全的非常有用的手段。
先行发生是Java内存模型中定义的两项操作之间的偏序关系。
A先于B发生,即在发生操作B之前,操作A的影响能被B观察到。
模型实现
程序次序规则,管程锁定规则,volatile变量规则,线程启动规则,线程终止规则,线程中断规则,对象终结规则,传递性
Java与线程
线程实现
内核线程实现
1:1实现
直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。
支持多线程的内核就称为多线程内核
用户线程实现
1:N实现
广义:只要不是内核线程,都可以认为是用户线程的一种
狭义:完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在及如何实现的。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。
混合实现
内核线程与用户线程一起使用
Java线程的实现
基于操作系统原生线程模型来实现,即采用1:1的线程模型。
线程调度
协同式线程调度和抢占式线程调度
协同式线程调度
线程的执行时间由线程本身来控制,执行完毕后,主动通知系统切换线程。
实现简单,切换操作对线程自己可知,一般无线程同步的问题。
抢占式线程调度
每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定
Java使用的线程调度方式就是抢占式调度
状态转换
新建、运行、无限期等待、限期等待、阻塞、结束
Java与协程
内核线程的局限
线程天然的缺陷是切换、调度成本高昂,系统能容纳的线程数量也很有限。
原因
内核线程的调度成本主要来自于用户态与核心态之间的状态转换,而这两种状态转换的开销主要来自于响应中断、保护和恢复执行现场的成本。
协程
用户线程是被设计成协同式调度:协程/有栈协程
有栈协程,有一种特例实现名为纤程
使用纤程并发的代码会被分为两部分:执行过程、调度器
执行过程:维护执行现场,保护、恢复上下文状态.
调度器:编排所有要执行的代码的顺序。