小編的話
在文章的開始作者為大家整理了很多資料递鹉!包括一線大廠Java面試題總結(jié)+各知識(shí)點(diǎn)學(xué)習(xí)思維導(dǎo)+一份300頁pdf文檔的Java核心知識(shí)點(diǎn)總結(jié)! 這些資料的內(nèi)容都是面試時(shí)面試官必問的知識(shí)點(diǎn)藏斩,篇章包括了很多知識(shí)點(diǎn)躏结,其中包括了有基礎(chǔ)知識(shí)、Java集合狰域、JVM媳拴、多線程并發(fā)、spring原理兆览、微服務(wù)屈溉、Netty 與RPC 、Kafka抬探、日記子巾、設(shè)計(jì)模式、Java算法小压、數(shù)據(jù)庫砰左、Zookeeper、分布式緩存场航、數(shù)據(jù)結(jié)構(gòu)等等缠导。
你要是需要的話點(diǎn)這里直接下載就好了,希望對(duì)你有幫助
Java內(nèi)存模型
簡單介紹一下Java內(nèi)存模型
Java內(nèi)存模型即Java Memory Model溉痢,簡稱JMM僻造。JMM定義了Java 虛擬機(jī)(JVM)在計(jì)算機(jī)內(nèi)存(RAM)中的工作方式。JVM是整個(gè)計(jì)算機(jī)虛擬模型孩饼,所以JMM是隸屬于JVM的髓削。
Java內(nèi)存模型是共享內(nèi)存的并發(fā)模型,線程之間主要通過讀-寫共享變量(堆內(nèi)存中的實(shí)例域镀娶,靜態(tài)域和數(shù)組元素)來完成隱式通信立膛。Java 內(nèi)存模型(JMM)控制 Java 線程之間的通信,決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見。
JVM主內(nèi)存與工作內(nèi)存
Java 內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則宝泵,即在虛擬機(jī)中將變量(線程共享的變量)存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣底層細(xì)節(jié)好啰。
Java內(nèi)存模型中規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存儿奶,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行同衣,而不能直接讀寫主內(nèi)存中的變量古今。這里的工作內(nèi)存是 JMM 的一個(gè)抽象概念,也叫本地內(nèi)存,其存儲(chǔ)了該線程以讀 / 寫共享變量的副本夺衍。
就像每個(gè)處理器內(nèi)核擁有私有的高速緩存芥被,JMM 中每個(gè)線程擁有私有的本地內(nèi)存诽里。不同線程之間無法直接訪問對(duì)方工作內(nèi)存中的變量手负,線程間的通信一般有兩種方式進(jìn)行,一是通過消息傳遞茬祷,二是共享內(nèi)存沐飘。Java 線程間的通信采用的是共享內(nèi)存方式,線程牲迫、主內(nèi)存和工作內(nèi)存的交互關(guān)系如下圖所示:
線程A和線程B通信要經(jīng)過兩個(gè)步驟:
線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中
線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量
這里所講的主內(nèi)存耐朴、工作內(nèi)存與 Java 內(nèi)存區(qū)域中的 Java 堆、棧盹憎、方法區(qū)等并不是同一個(gè)層次的內(nèi)存劃分筛峭,這兩者基本上是沒有關(guān)系的,如果兩者一定要勉強(qiáng)對(duì)應(yīng)起來陪每,那從變量影晓、主內(nèi)存、工作內(nèi)存的定義來看檩禾,主內(nèi)存主要對(duì)應(yīng)于Java堆中的對(duì)象實(shí)例數(shù)據(jù)部分挂签,而工作內(nèi)存則對(duì)應(yīng)于虛擬機(jī)棧中的部分區(qū)域。
JMM數(shù)據(jù)原子操作
read(讀扰尾):從主內(nèi)存讀取數(shù)據(jù)
load(載入):將主內(nèi)存讀取到的數(shù)據(jù)寫入工作內(nèi)存
use(使用):從工作內(nèi)存讀取數(shù)據(jù)來計(jì)算
assign(賦值):將計(jì)算好的值重新賦值到工作內(nèi)存中
store(存儲(chǔ)):將工作內(nèi)存數(shù)據(jù)寫入主內(nèi)存
write(寫入):將store過去的變量值賦值給主內(nèi)存中的變量
lock(鎖定):將主內(nèi)存變量加鎖饵婆,標(biāo)識(shí)為線程獨(dú)占狀態(tài)
unlock(解鎖):將主內(nèi)存變量解鎖,解鎖后其他線程可以鎖定該變量
public class VolatileVisibilityTest {
private static volatile boolean initFlag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("waiting data...");
while (!initFlag) {
}
System.out.println("====================success");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
prepareDate();
}
}).start();
}
public static void prepareDate() {
System.out.println("preparing data...");
initFlag = true;
System.out.println("prepare end...");
}
}
計(jì)算機(jī)高速緩存和緩存一致性
計(jì)算機(jī)在高速的 CPU 和相對(duì)低速的存儲(chǔ)設(shè)備之間使用高速緩存戏售,作為內(nèi)存和處理器之間的緩沖侨核。將運(yùn)算需要使用到的數(shù)據(jù)復(fù)制到緩存中,讓運(yùn)算能快速運(yùn)行灌灾,當(dāng)運(yùn)算結(jié)束后再從緩存同步回內(nèi)存之中搓译。
在多處理器的系統(tǒng)中(或者單處理器多核的系統(tǒng)),每個(gè)處理器內(nèi)核都有自己的高速緩存锋喜,它們有共享同一主內(nèi)存(Main Memory)些己。當(dāng)多個(gè)處理器的運(yùn)算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時(shí),將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致。
為此段标,需要各個(gè)處理器訪問緩存時(shí)都遵循一些協(xié)議涯冠,在讀寫時(shí)要根據(jù)協(xié)議進(jìn)行操作,來維護(hù)緩存的一致性怀樟。
MESI緩存一致性協(xié)議:多個(gè)CPU從主存讀取同一個(gè)數(shù)據(jù)到各自的告訴緩存,當(dāng)其中某個(gè)CPU修改了緩存里的數(shù)據(jù)盆佣,該數(shù)據(jù)會(huì)馬上同步會(huì)主內(nèi)存往堡,其他CPU通過總線嗅探機(jī)制可以感知到數(shù)據(jù)的變化從而將自己緩存里的數(shù)據(jù)失效。
volatile緩存可見性實(shí)現(xiàn)原理
底層實(shí)現(xiàn)主要通過匯編lock前綴指令共耍,它會(huì)鎖定這塊內(nèi)存區(qū)域的緩存(緩存行鎖定)并回寫到主存
1)會(huì)將當(dāng)前處理器緩存行的數(shù)據(jù)立即寫回到系統(tǒng)內(nèi)存
2)這個(gè)寫回內(nèi)存的操作會(huì)引起在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效(MESI)
重排序和happens-before規(guī)則
在執(zhí)行程序時(shí)為了提高性能虑灰,編譯器和處理器常常會(huì)對(duì)指令做重排序。重排序分三種類型:
編譯器優(yōu)化的重排序痹兜。編譯器在不改變單線程程序語義的前提下穆咐,可以重新安排語句的執(zhí)行順序。
指令級(jí)并行的重排序∽中瘢現(xiàn)代處理器采用了指令級(jí)并行技術(shù)(Instruction-Level Parallelism对湃, ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性遗淳,處理器可以改變語句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序拍柒。
內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀 / 寫緩沖區(qū)屈暗,這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行拆讯。
從 java 源代碼到最終實(shí)際執(zhí)行的指令序列,會(huì)分別經(jīng)歷下面三種重排序:
JMM 屬于語言級(jí)的內(nèi)存模型养叛,它確保在不同的編譯器和不同的處理器平臺(tái)之上种呐,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內(nèi)存可見性保證弃甥。java 編譯器禁止處理器重排序是通過在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障(重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置)指令來實(shí)現(xiàn)的爽室。
happens-before
從 JDK5 開始,java 內(nèi)存模型提出了 happens-before 的概念淆攻,通過這個(gè)概念來闡述操作之間的內(nèi)存可見性肮之。如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見,那么這兩個(gè)操作之間必須存在 happens-before 關(guān)系卜录。這里提到的兩個(gè)操作既可以是在一個(gè)線程之內(nèi)戈擒,也可以是在不同線程之間。這里的“可見性”是指當(dāng)一條線程修改了這個(gè)變量的值艰毒,新值對(duì)于其他線程來說是可以立即得知的筐高。
如果 A happens-before B,那么 Java 內(nèi)存模型將向程序員保證—— A 操作的結(jié)果將對(duì) B 可見,且 A 的執(zhí)行順序排在 B 之前柑土。
重要的 happens-before 規(guī)則如下:
程序順序規(guī)則:一個(gè)線程中的每個(gè)操作蜀肘,happens- before 于該線程中的任意后續(xù)操作。
監(jiān)視器鎖規(guī)則:對(duì)一個(gè)監(jiān)視器鎖的解鎖稽屏,happens- before 于隨后對(duì)這個(gè)監(jiān)視器鎖的加鎖扮宠。
volatile 變量規(guī)則:對(duì)一個(gè) volatile 域的寫,happens- before 于任意后續(xù)對(duì)這個(gè) volatile 域的讀狐榔。
傳遞性:如果 A happens- before B坛增,且 B happens- before C,那么 A happens- before C薄腻。
下圖是 happens-before 與 JMM 的關(guān)系
最后
在文章的最后作者為大家整理了很多資料收捣!包括一線大廠Java面試題總結(jié)+各知識(shí)點(diǎn)學(xué)習(xí)思維導(dǎo)+一份300頁pdf文檔的Java核心知識(shí)點(diǎn)總結(jié)! 這些資料的內(nèi)容都是面試時(shí)面試官必問的知識(shí)點(diǎn)庵楷,篇章包括了很多知識(shí)點(diǎn)罢艾,其中包括了有基礎(chǔ)知識(shí)、Java集合尽纽、JVM咐蚯、多線程并發(fā)、spring原理弄贿、微服務(wù)仓蛆、Netty 與RPC 、Kafka挎春、日記看疙、設(shè)計(jì)模式、Java算法直奋、數(shù)據(jù)庫能庆、Zookeeper、分布式緩存脚线、數(shù)據(jù)結(jié)構(gòu)等等搁胆。
歡迎關(guān)注公眾號(hào):前程有光,領(lǐng)扔事獭渠旁!