java多线程内存模型和CPU缓存模型类似,是基于CPU缓存模型来建立的,JAVA线程内存模型是标准化的,屏蔽掉了底层不同计算机的区别。
线程 - 工作内存(共享变量副本) == JVM控制 == 主内存(共享变量)
比如:
线程1 read 主线程变量flag,load到工作内存,然后use,use后assign给工作内存,然后store到主内存,然后write给主内存变量。此时线程2中的共享变量flag副本是感知不到变量已经变化了。
那为什么加volatile后线程2中就能感知到了呢?
多个CPU从主内存读取同一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,该数据会马上同步回主内存,其他cpu通过总线嗅探机制可以告知到数据的变化,从而将自己缓存里的数据失效。
缓存锁的核心是基于缓存一致性协议来实现的,一个处理器的缓存写回到内存会导致其他处理器的缓存无效。IA-32和Intel 64处理器使用MESI实现缓存一致性协议。
底层通过汇编lock前缀指令,锁定这块内存区域的缓存(缓存行锁定),并写回主内存
IA-32和Intel64架构软件开发者手册对lock指令的解释:
需要工具包(hsdis
)
-server -Xcopm -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=comlileonly,*XxxxClassName.methodName
//这行代码在底层可能被重排序,所以把instance 用volatile修饰
instance = new DoubleCheckLockSingleton();
/**
* 说明: volatile 测试有序性
* 双重锁检测DCL 问题
*
* @author Vic.xu
* @since 2023/6/29/0029 16:27
*/
public class DoubleCheckLockSingleton {
private static DoubleCheckLockSingleton instance = null;
private DoubleCheckLockSingleton() {
}
//DCL
public static DoubleCheckLockSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckLockSingleton.class) {
if (instance == null) {
//这行代码在底层可能被重排序,所以把instance 用volatile修饰
instance = new DoubleCheckLockSingleton();
}
}
}
return instance;
}
public static void main(String[] args) {
DoubleCheckLockSingleton instance = DoubleCheckLockSingleton.getInstance();
}
}
类加载检测 →是否已经加载 →(否 ,加载类)→分配内存 → 初始化零值 → 设置对象头 → 执行init方法
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一类的符号引用,并且检测这个符号引用代表的类是否已经被加载、解析和初始化。如果没有,那必须先执行相应的类加载过程。
new指令对应到语言层面上讲是,new关键词,对象克隆、对象序列化等
在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。
这一步有两个问题:
执行init方法,即对象按照程序员的意思进行初始化。对应到语言层面讲,就是为属性赋值(注意,这与上面的赋零值不同,这是由程序员赋的值),和执行构造方法
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad | Load1;LoadLoad;Load2 | 保证load1的读取操作在load2及后续读取操作之前执行 |
StoreStore | Store1;StoreStore;Store2 | 在store2机器后的写操作执行之前,保证store1的写操作已经刷到主内存 |
LoadStore | Load1;LoadStoreStore2 | 在store2及其后的写操作之前,保证load1的读操作已读取结束 |
StoreLoad | Store1;StoreLoad;Load2 | 保证store1的写操作已经刷新到主内存之后,load2机器后续操作才能执行 |
StoreStore屏障
a=1;//volatile写,a为volatile变量
StoreLoad屏障
b=a;// volatile变量
LoadLoad屏障
LoadStore屏障
不同CPU硬件对于JVM的内存屏障规范实现不一样
Intek CPU硬件级内存屏障实现指令
sfence:
mfence:
JVM底层简化了内存屏障硬件指令的实现
比如native compareAndSwapInt
不一定
多个线程自旋,CPU空转,耗CPU比较多,还不如放入等待队列
线程池状态
AtomicInteger ctl:表示线程状态和线程数
判断线程状态
判断线程数,是否新开线程
线程start,表示线程就绪
SimulateLock{
// 0-默认没有加锁;
// 非0,标识加锁了,1-加锁成功1次,2-加锁成功2次....
volatile int state;
static Unsafe unsafe;
private static long stateOffset;
public void lock(){
// t1加锁成功,标识state +1
//判断当前线程位t1
while(!compareAndSet(0,1)){}
}
}
https://www.bilibili.com/video/BV1c44y1U7em/?p=169
tryAcquire 和nonfairTryAcquire