处理器快,其他太慢了,包括访存,加入高速缓存
高速缓存带来缓存一致性问题:同一块主存,各自的缓存不一样,缓存与主存交互时候需通过缓存一致性协议
要讲清楚java内存模型:先讲为什么需要jmm,然后分6个点展开讲,1主存/工作内存 2 8个内存间交互操作 3 volatile特殊规则 4 long和double特殊规则 5 原子/可见/有序性 6 happens-before
jmm 屏蔽硬件和操作系统差异,定义(共享)变量访问规则
变量存在主存,线程操作变量必须工作内存中进行(包括volatile),工作内存含主存变量副本
低层次:一般来说,主存对应内存,工作内存对应寄存器和高速缓存
8原子操作
lock 将主内存变量标识为线程独占
unlock
read 变量值传到工作内存
load 变量值放入工作内存变量副本中
use 变量值传给执行引擎,如放入操作栈顶(getstatic最后一步)
assign 执行引擎传值给变量副本
store 变量值传到主存
write 变量值放入主存变量中
long和double的非原子性协定: 非volatile的long,double可拆分两个32bit的操作(非原子性),有可能读到一个修改一半的数据,但很罕见,因为实际虚拟机大多还是实现成原子的。
变量只能在主存诞生,
lock,清空工作内存此变量值,执行引擎使用前必须load或assign
unlock之前必须把此变量同步回主存
volatile
可见性 禁止指令重排序(包括编译重排和乱序执行) 处理器重排序(乱序执行) jvm(jit)重排序
可见性:jmm规定volatile,use和read,load绑定,assign和store,write绑定
实现:汇编代码加入lock前缀指令相当于内存屏障
原子性:基本数据类型读写(6个原子操作,long,double不用太在意)
lock/unlock monitorenter/monitorexit synchronized(大范围原子操作)
synchronized原子性(A,B两个赋值操作,其它线程观测不到非原子的状态(A赋值B没有的情况),因为其它线程此时无法获取到锁)
可见性:volatile,synchronized(lock清空,unlock同步),final(不可变性使访问线程拿到的值肯定是对的,但发生this引用逸出导致第一次拿到的final域还没赋值,final就变化了)
有序性:
线程内表现为串行 会有各种重排序,但在一个线程内感知不出来,因为重排序只发生在无依赖关系处
但其他线程观测到的是无序的,因为其他线程对重排序变量有依赖关系和内存同步延迟
B观测A带来的变化和影响,这就是有序的(A先行发生于B,A happens-before B),没有先序发生,可任意重排序
程序次序(同一线程,A写在B前面,A happens-before B)
管程锁定(unlock happens-before 时间上后发生的lock)
volatile
thread.start()
thread.join()/isAlive()
thread.interrupt()
对象构造 happens-before finalize()
A happens-before B B happens-before C - A happens-before C
happens-before和发生时间前后无关
A时间先,但不happens-before(普通变量读写)
Ahappens-before,但不时间先(指令重排序)
线程实现
内核线程:单个内核线程暴露给程序的接口是轻量级进程,我们程序就直接使用轻量级进程,1:1,(Sun jdk windows,linux 都是这种)
用户线程:cpu时间分给进程,进程内多个线程自己分配切换(自己得有线程库),1:N。
用户线程加轻量级进程:N:M
线程调度
协同式:必须线程主动放弃cpu
抢占式:系统分配时间,java
线程优先级:建议cpu时间多分少分,不太靠谱(依赖于操作系统,不同优先级可能最后映射成相同的,甚至系统会改变优先级)
状态:new,runnable,waiting,timed_waiting,blocked,terminated
blocked 获取排他锁
waiting 无限期等待 object.wait() thread.join() Locksupport.park()
timed_waiting thread.sleep() 带timeout的object.wait() 带timeout的thread.join() Locksupport.parkUtil() Locksupport.parkNanos()