[TOC]
## Java 內存模型(JMM)
Java 內存模型試圖屏蔽各種硬件和操作系統的內存訪問差異,以實現讓 Java 程序在各種平臺下都能達到一致的內存訪問效果。
### 主內存與工作內存
處理器上的寄存器的讀寫的速度比內存快幾個數量級,為了解決這種速度矛盾,在它們之間加入了高速緩存。
加入高速緩存帶來了一個新的問題:緩存一致性。如果多個緩存共享同一塊主內存區域,那么多個緩存的數據可能會不一致,需要一些協議來解決這個問題。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/1195582-20180508173147029-1341787720.png)
所有的變量都存儲在**主內存**中,每個線程還有自己的**工作內存**,工作內存存儲在高速緩存或者寄存器中,保存了該線程使用的變量的主內存副本拷貝。
線程只能直接操作工作內存中的變量,不同線程之間的變量值傳遞需要通過主內存來完成。
**Java內存模型和硬件關系圖**
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/v2-4015322359279c5568263aeb7f41c36d.jpg)
**Java內存模型抽象結構圖**
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/1135283-20170403195814660-1521573510.png)
### 內存間交互操作
Java 內存模型定義了 8 個操作來完成主內存和工作內存的交互操作。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/536c6dfd-305a-4b95-b12c-28ca5e8aa043.png)
* read:把一個變量的值從主內存傳輸到工作內存中
* load:在 read 之后執行,把 read 得到的值放入工作內存的變量副本中
* use:把工作內存中一個變量的值傳遞給執行引擎
* assign:把一個從執行引擎接收到的值賦給工作內存的變量
* store:把工作內存的一個變量的值傳送到主內存中
* write:在 store 之后執行,把 store 得到的值放入主內存的變量中
* lock:作用于主內存的變量,把一個變量標識為一條線程獨占狀態
* unlock:作用于主內存變量,把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定
如果要把一個變量從主內存中復制到工作內存,就需要按順尋地執行 read 和 load 操作,如果把變量從工作內存中同步回主內存中,就要按順序地執行 store 和 write 操作。Java內存模型只要求上述操作必須按順序執行,而沒有保證必須是連續執行。也就是 read 和 load 之間,store 和 write 之間是可以插入其他指令的,如對主內存中的變量a、b進行訪問時,可能的順序是read a,read b,load b, load a。
**Java內存模型還規定了在執行上述8種基本操作時,必須滿足如下規則:**
* 不允許 read 和 load、store 和 write 操作之一單獨出現
* 不允許一個線程丟棄它的最近 assign 的操作,即變量在工作內存中改變了之后必須同步到主內存中
* 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從工作內存同步回主內存中
* 一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load 或 assign)的變量。即就是對一個變量實施 use 和 store 操作之前,必須先執行過了 assign 和 load 操作。
* 一個變量在同一時刻只允許一條線程對其進行lock操作,lock 和 unlock必須成對出現
* 如果對一個變量執行 lock 操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行 load 或 assign 操作初始化變量的值
* 如果一個變量事先沒有被 lock 操作鎖定,則不允許對它執行 unlock 操作;也不允許去 unlock 一個被其他線程鎖定的變量。
* 對一個變量執行 unlock 操作之前,必須先把此變量同步到主內存中(執行 store 和 write 操作)。
參考資料:
[volatile關鍵字與Java內存模型(JMM) - yzwall - 博客園](https://www.cnblogs.com/yzwall/p/6661528.html)
### 內存模型三大特性
#### 1\. 原子性
* 概念
* 事物有原子性,這個概念大概都清楚,即一個操作或多個操作要么執行的過程中不被任何因素打斷,要么不執行。
* 如何實現原子性?
* 通過同步代碼塊 synchronized 或者 local 鎖來確保原子性
Java 內存模型保證了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如對一個 int 類型的變量執行 assign 賦值操作,這個操作就是原子性的。但是 Java 內存模型允許虛擬機將沒有被 volatile 修飾的 64 位數據(long,double)的讀寫操作劃分為兩次 32 位的操作來進行,即 load、store、read 和 write 操作可以不具備原子性。
有一個錯誤認識就是,int 等原子性的變量在多線程環境中不會出現線程安全問題。前面的線程不安全示例代碼中,cnt 變量屬于 int 類型變量,1000 個線程對它進行自增操作之后,得到的值為 997 而不是 1000。
為了方便討論,將內存間的交互操作簡化為 3 個:load、assign、store。
下圖演示了兩個線程同時對 cnt 變量進行操作,load、assign、store 這一系列操作整體上看不具備原子性,那么在 T1 修改 cnt 并且還沒有將修改后的值寫入主內存,T2 依然可以讀入該變量的值。可以看出,這兩個線程雖然執行了兩次自增運算,但是主內存中 cnt 的值最后為 1 而不是 2。因此對 int 類型讀寫操作滿足原子性只是說明 load、assign、store 這些單個操作具備原子性。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/ef8eab00-1d5e-4d99-a7c2-d6d68ea7fe92-1534148019548.png)
AtomicInteger 能保證多個線程修改的原子性。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/952afa9a-458b-44ce-bba9-463e60162945-1534148027104.png)
使用 AtomicInteger 重寫之前線程不安全的代碼之后得到以下線程安全實現:
~~~java
public class AtomicExample {
private AtomicInteger cnt = new AtomicInteger();
public void add() {
cnt.incrementAndGet();
}
public int get() {
return cnt.get();
}
}
~~~
~~~java
public static void main(String[] args) throws InterruptedException {
final int threadSize = 1000;
AtomicExample example = new AtomicExample(); // 只修改這條語句
final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threadSize; i++) {
executorService.execute(() -> {
example.add();
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println(example.get());
}
~~~
~~~
1000
~~~
除了使用原子類之外,也可以使用 synchronized 互斥鎖來保證操作的原子性。它對應的內存間交互操作為:lock 和 unlock,在虛擬機實現上對應的字節碼指令為 monitorenter 和 monitorexit。
~~~java
public class AtomicSynchronizedExample {
private int cnt = 0;
public synchronized void add() {
cnt++;
}
public synchronized int get() {
return cnt;
}
}
public static void main(String[] args) throws InterruptedException {
final int threadSize = 1000;
AtomicSynchronizedExample example = new AtomicSynchronizedExample();
final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threadSize; i++) {
executorService.execute(() -> {
example.add();
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println(example.get());
}
~~~
~~~
1000
~~~
#### 2\. 可見性
可見性指當一個線程修改了共享變量的值,其它線程能夠立即得知這個修改。Java 內存模型是通過在變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值來實現可見性的。
主要有有三種實現可見性的方式:
* volatile
* synchronized,對一個變量執行 unlock 操作之前,必須把變量值同步回主內存。
* final,被 final 關鍵字修飾的字段在構造器中一旦初始化完成,并且沒有發生 this 逃逸(其它線程通過 this 引用訪問到初始化了一半的對象),那么其它線程就能看見 final 字段的值。
對前面的線程不安全示例中的 cnt 變量使用 volatile 修飾,不能解決線程不安全問題,因為 volatile 并不能保證操作的原子性。
#### 3\. 有序性
有序性是指:在本線程內觀察,所有操作都是有序的。在一個線程觀察另一個線程,所有操作都是無序的,無序是因為發生了指令重排序。
在 Java 內存模型中,允許編譯器和處理器對指令進行重排序,重排序過程不會影響到單線程程序的執行,卻會影響到多線程并發執行的正確性。
volatile 關鍵字通過添加內存屏障的方式來禁止指令重排,即重排序時不能把后面的指令放到內存屏障之前。
也可以通過 synchronized 來保證有序性,它保證每個時刻只有一個線程執行同步代碼,相當于是讓線程順序執行同步代碼。
### 指令重排序
在執行程序時為了提高性能,編譯器和處理器常常會對指令做重排序。
指令重排序包括:**編譯器重排序**和**處理器重排序**
重排序分三種類型:
1. **編譯器優化的重排序**。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。
2. **指令級并行的重排序**。現代處理器采用了指令級并行技術(Instruction-Level Parallelism, ILP)來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
3. **內存系統的重排序**。由于處理器使用緩存和讀/寫緩沖區,這使得加載和存儲操作看上去可能是在亂序執行。
從 Java 源代碼到最終實際執行的指令序列,會分別經歷下面三種重排序:
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/33-1534150864535.png)
**上述的 1 屬于編譯器重排序,2 和 3 屬于處理器重排序**。這些重排序都可能會導致多線程程序出現內存可見性問題。對于編譯器,JMM 的編譯器重排序規則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。對于處理器重排序,JMM 的處理器重排序規則會要求 Java 編譯器在生成指令序列時,插入特定類型的內存屏障(memory barriers,intel 稱之為 memory fence)指令,通過內存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。
JMM 屬于語言級的內存模型,它確保在不同的編譯器和不同的處理器平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內存可見性保證。
#### 數據依賴性
如果兩個操作訪問同一個變量,且這兩個操作中有一個為寫操作,此時這兩個操作之間就存在數據依賴性。數據依賴分下列三種類型:
| 名稱 | 代碼示例 | 說明 |
| --- | --- | --- |
| 寫后讀 | a = 1;b = a; | 寫一個變量之后,再讀這個位置。 |
| 寫后寫 | a = 1;a = 2; | 寫一個變量之后,再寫這個變量。 |
| 讀后寫 | a = b;b = 1; | 讀一個變量之后,再寫這個變量。 |
上面三種情況,只要重排序兩個操作的執行順序,程序的執行結果將會被改變。
前面提到過,編譯器和處理器可能會對操作做重排序。編譯器和處理器在重排序時,會遵守數據依賴性,編譯器和處理器不會改變存在數據依賴關系的兩個操作的執行順序。
注意,這里所說的數據依賴性僅針對單個處理器中執行的指令序列和單個線程中執行的操作,不同處理器之間和不同線程之間的數據依賴性不被編譯器和處理器考慮。
#### as-if-serial語義
as-if-serial 語義的意思指:不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執行結果不能被改變。編譯器,runtime 和 處理器 都必須遵守 as-if-serial 語義。
為了遵守 as-if-serial 語義,編譯器和處理器不會對存在數據依賴關系的操作做重排序,因為這種重排序會改變執行結果。但是,如果操作之間不存在數據依賴關系,這些操作可能被編譯器和處理器重排序。為了具體說明,請看下面計算圓面積的代碼示例:
~~~java
double pi = 3.14; //A
double r = 1.0; //B
double area = pi * r * r; //C
~~~
上面三個操作的數據依賴關系如下圖所示:
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/11.png)
如上圖所示,A 和 C 之間存在數據依賴關系,同時 B 和 C 之間也存在數據依賴關系。因此在最終執行的指令序列中,C 不能被重排序到 A 和 B 的前面(C 排到 A 和 B 的前面,程序的結果將會被改變)。但 A 和 B 之間沒有數據依賴關系,編譯器和處理器可以重排序 A 和 B 之間的執行順序。下圖是該程序的兩種執行順序:
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/22.png)
as-if-serial 語義把單線程程序保護了起來,遵守 as-if-serial 語義的編譯器,runtime 和處理器共同為編寫單線程程序的程序員創建了一個幻覺:單線程程序是按程序的順序來執行的。as-if-serial 語義使單線程程序員無需擔心重排序會干擾他們,也無需擔心內存可見性問題。
#### 程序順序規則
根據 happens- before 的程序順序規則,上面計算圓的面積的示例代碼存在三個 happens- before 關系:
1. A happens- before B;
2. B happens- before C;
3. A happens- before C;
這里的第 3 個 happens- before 關系,是根據 happens- before 的傳遞性推導出來的。
這里 A happens- before B,但實際執行時 B 卻可以排在 A 之前執行(看上面的重排序后的執行順序)。如果A happens- before B,JMM 并不要求 A 一定要在 B 之前執行。JMM 僅僅要求前一個操作(執行的結果)對后一個操作可見,且前一個操作按順序排在第二個操作之前。這里操作 A 的執行結果不需要對操作 B 可見;而且重排序操作 A 和操作 B 后的執行結果,與操作 A 和操作 B 按 happens- before 順序執行的結果一致。在這種情況下, JMM 會認為這種重排序并不非法(not illegal),JMM 允許這種重排序。
在計算機中,軟件技術和硬件技術有一個共同的目標:在不改變程序執行結果的前提下,盡可能的開發并行度。編譯器和處理器遵從這一目標,從 happens- before 的定義我們可以看出,JMM 同樣遵從這一目標。
#### 重排序對多線程的影響
現在讓我們來看看,重排序是否會改變多線程程序的執行結果。請看下面的示例代碼:
~~~java
class ReorderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2
}
Public void reader() {
if (flag) { // 3
int i = a * a; // 4
……
}
}
}
~~~
flag 變量是個標記,用來標識變量 a 是否已被寫入。這里假設有兩個線程 A 和 B,A首先執行 writer() 方法,隨后 B 線程接著執行 reader() 方法。線程 B 在執行操作 4 時,能否看到線程 A 在操作 1 對共享變量 a 的寫入?
答案是:不一定能看到。
由于操作 1 和操作 2 沒有數據依賴關系,編譯器和處理器可以對這兩個操作重排序;同樣,操作 3 和操作 4 沒有數據依賴關系,編譯器和處理器也可以對這兩個操作重排序。讓我們先來看看,當操作 1 和操作 2 重排序時,可能會產生什么效果?請看下面的程序執行時序圖:
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/33.png)
如上圖所示,操作 1 和操作 2 做了重排序。程序執行時,線程 A 首先寫標記變量 flag,隨后線程 B 讀這個變量。由于條件判斷為真,線程 B 將讀取變量 a。此時,變量 a 還根本沒有被線程 A 寫入,在這里多線程程序的語義被重排序破壞了!
※注:本文統一用紅色的虛箭線表示錯誤的讀操作,用綠色的虛箭線表示正確的讀操作。
下面再讓我們看看,當操作 3 和操作 4 重排序時會產生什么效果(借助這個重排序,可以順便說明控制依賴性)。下面是操作 3 和操作 4 重排序后,程序的執行時序圖:
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/44.png)
在程序中,操作 3 和操作 4 存在控制依賴關系。當代碼中存在控制依賴性時,會影響指令序列執行的并行度。為此,編譯器和處理器會采用猜測(Speculation)執行來克服控制相關性對并行度的影響。以處理器的猜測執行為例,執行線程 B 的處理器可以提前讀取并計算 a\*a,然后把計算結果臨時保存到一個名為重排序緩沖(reorder buffer ROB)的硬件緩存中。當接下來操作3的條件判斷為真時,就把該計算結果寫入變量 i 中。
從圖中我們可以看出,猜測執行實質上對操作 3 和 4 做了重排序。重排序在這里破壞了多線程程序的語義!
在單線程程序中,對存在控制依賴的操作重排序,不會改變執行結果(這也是 as-if-serial 語義允許對存在控制依賴的操作做重排序的原因);但在多線程程序中,對存在控制依賴的操作重排序,可能會改變程序的執行結果。
參考資料:
* [深入理解Java內存模型(一)——基礎](http://www.infoq.com/cn/articles/java-memory-model-1)
* [深入理解Java內存模型(二)——重排序](http://www.infoq.com/cn/articles/java-memory-model-2)
### 先行發生原則(happens-before)
Happens-before 是用來指定兩個操作之間的執行順序。提供跨線程的內存可見性。
在 Java 內存模型中,如果一個操作執行的結果需要對另一個操作可見,那么這兩個操作之間必然存在 happens-before 關系。
上面提到了可以用 volatile 和 synchronized 來保證有序性。除此之外,JVM 還規定了先行發生原則,讓一個操作無需控制就能先于另一個操作完成。
主要有以下這些原則:
#### 1\. 單一線程原則
> Single Thread rule
在一個線程內,在程序前面的操作先行發生于后面的操作。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/single-thread-rule-1534148720379.png)
#### 2\. 管程鎖定規則
> Monitor Lock Rule
對一個鎖的解鎖(unlock ),總是 happens-before 于隨后對這個鎖的加鎖(lock)
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/monitor-lock-rule-1534148737603.png)
#### 3\. volatile 變量規則
> Volatile Variable Rule
對一個 volatile 變量的寫操作先行發生于后面對這個變量的讀操作。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/volatile-variable-rule-1534148747964.png)
#### 4\. 線程啟動規則
> Thread Start Rule
Thread 對象的 start() 方法調用先行發生于此線程的每一個動作。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/thread-start-rule-1534148760654.png)
#### 5\. 線程加入規則
> Thread Join Rule
Thread 對象的結束先行發生于 join() 方法返回。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/assets/thread-join-rule-1534148774041.png)
#### 6\. 線程中斷規則
> Thread Interruption Rule
對線程 interrupt() 方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過 interrupted() 方法檢測到是否有中斷發生。
#### 7\. 對象終結規則
> Finalizer Rule
一個對象的初始化完成(構造函數執行結束)先行發生于它的 finalize() 方法的開始。
#### 8\. 傳遞性
> Transitivity
如果操作 A 先行發生于操作 B,操作 B 先行發生于操作 C,那么操作 A 先行發生于操作 C。
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊