CPU知识补充
CPU的乱序执行
package com.mashibing.jvm.c3_jmm;
public class T04_Disorder {
private static int x = 0, y = 0;
private static int a = 0, b =0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for(;;) {
i++;
x = 0; y = 0;
a = 0; b = 0;
Thread one = new Thread(new Runnable() {
public void run() {
//由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
//shortWait(100000);
a = 1;
x = b;
}
});
Thread other = new Thread(new Runnable() {
public void run() {
b = 1;
y = a;
}
});
one.start();other.start();
one.join();other.join();
String result = "第" + i + "次 (" + x + "," + y + ")";
if(x == 0 && y == 0) {
System.err.println(result);
break;
} else {
//System.out.println(result);
}
}
}
public static void shortWait(long interval){
long start = System.nanoTime();
long end;
do{
end = System.nanoTime();
}while(start + interval >= end);
}
}
乱序执行可能会出现问题(存在于多线程)->DCL为什么要volatile?->禁止指令重排序
创建对象时,会有一个中间态,此时成员变量都是默认值
如果不加volatile,可能会发生指令重排,对象指针提前指向赋默认值的变量,此时其他线程来拿该对象,则会返回半初始化状态的对象
CPU层面如何禁止指令重排序?
有序性保障
CPU层级
-
X86CPU内存屏障
- sfence:在sfence指令前的写操作必须在sfence指令后的写操作前完成
- Infence:在sfence指令前的读操作必须在sfence指令后的读操作前完成
- mfence:在mfence指令前的读写操作必须在mfence指令后的读写操作前完成
-
intel lock汇编指令
原子指令,如X86上的“lock……”指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。
SoftWare Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。
JVM层级
JSR内存屏障
-
LoadLoad屏障:
对于这样的语句Load1,LoadLoad,Load2
在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。 -
StoreStore屏障:
对于这样的语句Store1,StoreStore,Store2
在Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见。 -
LoadStore屏障:
对于这样的语句Load1,LoadStore,Store2
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。 -
StoreLoad屏障:
对于这样的语句Store1,StoreLoad,Load2
在load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
拓展:volatile的实现细节
在JVM层面
底层屏障由lock指令实现的
拓展:happens-before原则
as if serial:不管如何重排序,单线程执行结果不会改变,看上去像是serial。
JVM层级:8个hanppens-before原则 4个内存屏障 (LL LS SL SS)
合并写
一般是4个字节
由于ALU速度太快,所以在写入L1的同时,写入一个WC Buffer,满了之后,再直接更新到L2。
package com.mashibing.juc.c_029_WriteCombining;
public final class WriteCombining {
private static final int ITERATIONS = Integer.MAX_VALUE;
private static final int ITEMS = 1 << 24;
private static final int MASK = ITEMS - 1;
private static final byte[] arrayA = new byte[ITEMS];
private static final byte[] arrayB = new byte[ITEMS];
private static final byte[] arrayC = new byte[ITEMS];
private static final byte[] arrayD = new byte[ITEMS];
private static final byte[] arrayE = new byte[ITEMS];
private static final byte[] arrayF = new byte[ITEMS];
public static void main(final String[] args) {
for (int i = 1; i <= 3; i++) {
System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne());
System.out.println(i + " SplitLoop duration (ns) = " + runCaseTwo());
}
}
public static long runCaseOne() {
long start = System.nanoTime();
int i = ITERATIONS;
//这个循环一共写6次
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayA[slot] = b;
arrayB[slot] = b;
arrayC[slot] = b;
arrayD[slot] = b;
arrayE[slot] = b;
arrayF[slot] = b;
}
return System.nanoTime() - start;
}
public static long runCaseTwo() {
long start = System.nanoTime();
int i = ITERATIONS;
//每个循环4次写操作
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayA[slot] = b;
arrayB[slot] = b;
arrayC[slot] = b;
}
i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayD[slot] = b;
arrayE[slot] = b;
arrayF[slot] = b;
}
return System.nanoTime() - start;
}
}
NUMA
UMA:多个CPU共享同一个内存
缺点:不易拓展,CPU数量增多后会引起内存访问冲突加剧。CPU很多资源浪费在争夺内存地址上。4颗合适
NUMA(non uniform memory access):非统一访问内存。从主板上会将cpu与内存分为不同的槽
ZGC使用NUMA-Aware:分配内存会优先分配该线程所在CPU的最近内存
操作系统基本知识
启动
通电 -> bios uefi 工作 -> 自检 -> 到硬盘固定位置加载bootloader -> 读取可配置信息 -> CMOS
什么是操作系统
操作系统主要做什么
简要结构
内核kernel
宏内核
用途:PC phone
微内核
用途:弹性部署 5G IoT
核心就是进程调度
外核(了解即可)
用途:科研 实验中 为应用定制操作系统 (多租户 request-based GC JVM)
VMM
虚拟机监控器
用户态与内核态
cpu分不同的指令级别
linux内核跑在ring 0级,用户程序跑在ring 3级,至于ring 1、ring 2都不使用;对于系统的关键访问,需要经过kernel的同意,保证系统健壮性
内核执行的操作 - > 只有200多个系统调用 sendfile read write pthread fork
JVM -> 站在OS老大的角度,就是个普通程序,因此在用户态