相關書籍推薦
讀書的原則:不求甚解,觀其大略
-
《編碼:隱匿在計算機軟硬件背后的語言》
-
《深入理解計算機系統》
-
語言:C JAVA K&R《C程序設計語言》《C Primer Plus》
-
數據結構與算法: --畢生的學習 leetCode
- 《Java數據結構與算法》《算法》
- 《算法導論》《計算機程序設計藝術》//難
-
操作系統:Linux內核源碼解析 Linux內核設計與實現 《30天自制操作系統》
-
網絡:機工《TCP/IP詳解》卷一 翻譯一般
-
編譯原理:機工 龍書 《編譯原理》 《編程語言實現模式》馬語
-
數據庫:SQLite源碼 Derby - JDK自帶數據庫
硬件基礎知識
CPU的制作過程
Intel cpu的制作過程:https://haokan.baidu.com/v?vid=11928468945249380709&pd=bjh&fr=bjhauthor&type=video
CPU是如何制作的(文字描述)
https://www.sohu.com/a/255397866_468626
CPU的原理
計算機需要解決的最根本問題:如何代表數字
晶體管是如何工作的:https://haokan.baidu.com/v?vid=16026741635006191272&pd=bjh&fr=bjhauthor&type=video
晶體管的工作原理:https://www.bilibili.com/video/av47388949?p=2
匯編語言(機器語言)的執行過程
匯編語言的本質:就算機器語言的助記符,其實它就是機器語言
計算機通電 -> CPU讀取內存中程序(電信號輸入)
->時鍾發生器不斷震盪通斷電:推動CPU內部一步一步執行(執行多少步取決於指令需要的時鍾周期)
->計算完成
->寫回(電信號)
->寫給顯卡輸出(sout,或者圖形)
計算機的組成
CPU的基本組成
- PC: Program Counter 程序計數器 (記錄當前指令地址)
- Registers: 暫時存儲CPU計算需要用到的數據
- ALU:Arithmetic & Logic Unit 運算單元
- CU: Control Unit 控制單元
- MMU:Memory Management Unit 內存管理單元
- cache
緩存一致性協議:https://www.cnblogs.com/z00377750/p/9180644.html
緩存行:
緩存行越大,局部性空間效率越高,但讀取時間慢
緩存行越小,局部性空間效率越低,但讀取時間快
取一個折中值,目前多用:64字節
測試緩存:Test1 時間比Test2 時間執行大概慢1秒左右
對比一下
public class Test1 {
public static volatile long[] arr = new long[2];
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 10_0000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 10_0000_0000L; i++) {
arr[1] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
public class Test2 {
public static volatile long[] arr = new long[16];
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 10_0000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 10_0000_0000L; i++) {
arr[8] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
經過測試,Test1類執行時間大概慢於Test2執行時間2秒左右
這是因為緩存行的原因:對於特別敏感的數字,會存在線程競爭的訪問,為了保證不發生偽共享,所以使用緩存行對齊的方式。
JDK7中,很多采用long padding提高效率
JDK8,加入了@Contended注解(實驗)需要加上:JVM配置項 -XX:-RestrictContended
可以試一下
public class Test1 {
@Contended
public static volatile long[] arr = new long[2];
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 10_0000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 10_0000_0000L; i++) {
arr[1] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
亂序執行
參考:https://preshing.com/20120515/memory-reordering-caught-in-the-act/
指令重排序的樣例:
public class 指令重排序Demo {
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() {
@Override
public void run() {
//由於線程one先啟動,下面這句話讓它等一等線程two. 讀着可根據自己電腦的實際性能適當調整等待時間.
//shortWait(100000);
a = 1;
x = b;
}
});
Thread other = new Thread(new Runnable() {
@Override
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); // 肯定會有x 和 y 都為0 的時候
break;
} else {
System.out.println(result);
}
}
}
}
這個測試要花好久時間:
DCL單例為什么要加volatile(Double Check Lock)
如下一個類:
public class T {
int m = 8;
}
如果要new一個對象:T t = new T();
它是會有一個中間態的,在new這個對象剛開始的時候,會在內存分配一塊空間,默認給這個int類型的m 賦值是0,
但如果是多線程情況在這個時候不斷地訪問,可能會得到m=0 這個值,是不對的,這是因為匯編碼的指令是亂序的,所以要禁止指令亂序。
CPU層面怎么禁止指令重排序?
內存屏障。
就是對某部分內存做操作時,前后添加屏障,屏障前后的操作不可以亂序執行。
Intel 使用的是原語
JVM層面怎么禁止指令重排序?
上面所說的是CPU層面的避免指令重排實現,也就是具體的實現,但java程序中是通過JVM的指令去調用CPU底層的
JVM層級:8個hanppens-before原則 4個內存屏障 (LL LS SL SS)
jvm 里面對於內存屏障的實現有4 種
- LoadLoad:對於這樣的語句
Load1;LoadLoad;Load2
,在Load2及后續的讀操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢; - StoreStore:對於這樣的語句
Store1;StoreStore;Store2
,在Store2及后續的寫操作執行前,保證Store1的寫入操作對其他處理器可見; - LoadStore:對於這樣的語句
Load1;LoadStore;Store2
,在Store2及后續的寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢; - StoreLoad:對於這樣的語句
Store1;StoreLoad;Load2
,在Load2及后續的讀操作要讀取的數據被訪問前,保證Store1的寫入操作對其他處理器可見;
StoreStoreBarrier
volatile 寫操作,上下不會亂排
StoreLoadBarrier
LoadLoadBarrier
volatile 讀操作,上下不會亂排
LoadStoreBarrier
合並寫技術的了解
Write Combining Buffer
一般是4個字節
由於ALU速度太快,所以在寫入L1的同時,寫入一個WC Buffer,滿了之后,再直接更新到L2
NUMA(Non Uniform Memory Access)
在ZGC - NUMA aware ,意思就是分配內存會優先分配該線程所在CPU的最近內存
先了解UMA:說白了多個cpu訪問一塊內存
再來看NUMA:每個cpu附近有個內存,訪問就近的內存比訪問別家的內存要快得多