悟空老師思維導圖:
https://naotu.baidu.com/file/60a0bdcaca7c6b92fcc5f796fe6f6bc9https://naotu.baidu.com/file/60a0bdcaca7c6b92fcc5f796fe6f6bc9
1.JVM內存結構&&Java內存模型&&Java對象模型
1.1.JVM內存結構
1.2.Java對象模型
????????Java對象模型表示的是這個對象本身的存儲模型,JVM會給這個類創建一個instanceKlass保存在方法區,用來在JVM層表示該Java類,當在Java代碼中使用new創建一個對象時JVM會創建一個instanceOopDesc對象,這個對象中包含了對象頭以及實例數據;
1.3.Java內存模型(JMM)
1.3.1.為什么需要JMM?
? ? ? ? 1.C語言不存在內存模型概念;
? ? ? ? 2.Java程序依賴處理器,不同處理器結果不一樣;
? ? ? ? 3.無法保證并發安全;
1.3.2.什么是JMM?
? ? ? ? JMM是一組規范,需要各個JVM的實現來遵循JMM規范,以便開發者可以利用這些規范更方便的開發多線程程序;如果沒有這樣一個JMM內存模型來規范,那么很可能經過了不同JVM的不同規則的重排序后,導致不同虛擬機上運行的結果不一樣;JMM不僅僅作為一組規范它同時還是“工具類”、“synchronized”、“Lock”等的原理;
1.3.3.JMM核心內容
? ? ? ? 1.重排序
? ? ? ? 2.可見性
? ? ? ? 3.原子性
? ? ? ? 并發編程線程安全問題的根源在于:重排序、可見性;
1.3.3.1.重排序
1.3.3.1.1.什么是重排序
? ? ? ? 代碼在JVM中的執行順序和在Java代碼中的順序不一致;(代碼指令執行順序并不是嚴格按照語句順序執行的,這就是重排序);
1.3.3.1.2.重排序代碼案例
import java.util.concurrent.CountDownLatch;
/*** 演示代碼執行時被JVM重排序*/
public class OutOfOrderExecution{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++;// 重置a = 0;b = 0;x = 0;y = 0;// 閘門CountDownLatch countDownLatch = new CountDownLatch(1);// 線程一Thread one = new Thread(new Runnable() {@Overridepublic void run() {try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}a = 1;x = b;}});// 線程二Thread two = new Thread(new Runnable() {@Overridepublic void run() {try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}b = 1;y = a;}});two.start();one.start();// 放開閘門countDownLatch.countDown();// 主線程等待子線程執行完成one.join();two.join();String result = "第:"+ i +"次" + "(" + x + "," + y + ")";if(x == 0 && y == 0){ // 說明:如上代碼出現x,y都等于0的情況說明代碼執行時被重排序了(即代碼并未按照編寫順序執行,而是被編譯器重排序了其執行順序大致為:y=a=0,x=b=0,b=1,a=1,)System.out.println("執行代碼被重排序了:" + result);break;}else {System.out.println(result);}}}
}
執行結果
1.3.3.1.3.重排序好處
1.3.3.1.4.重排序的三種情況
? ? ? ? 1.編譯器優化;
? ? ? ? 2.CPU指令重排;
? ? ? ? 3.內存的“重排序”
1.3.3.2.可見性
1.3.3.2.1.什么是“可見性”問題
? ? ? ? 可見性:指一個線程對共享變量的修改對于其它線程是可見的;
? ? ? ? 可見性問題:多線程并發訪問共享變量時,一個線程對共享變量的修改對于其它線程可能是不
??????????????????????????????可見的;
1.3.3.2.2.為什么會有“可見性”問題
????????因為CPU有多級緩存,導致某些線程讀取到的數據可能已經過期;如果所有CPU核心都只用一個緩存,那就不存在可見性問題;而實際情況是每個核心都會將需要的數據讀取到自己的“獨占緩存”中,數據修改后也是先寫入到自己的“獨占緩存”,然后等待刷新到“主存”(所有核心共享)中,在數據還未被刷新到“主存”時造成其它核心讀取到過期的數據值;
1.3.3.2.3.什么是happens-before
????????happens-before規則是用來解決“可見性”問題的,即在時間上動作A發生在動作B之前,B保證能看見A的所有操作這就是happens-before;
1.3.2.2.4.哪些運用了happens-before規則
? ? ? ? 1.單線程規則;
? ? ? ? 2.鎖操作(synchroniezd和Lock);
? ? ? ? 3.volatile變量;
? ? ? ? 4.線程啟動;
? ? ? ? 5.線程join;
? ? ? ? 6.傳遞性(hb代表happens-before;?如果hb(A,B),且hb(B,C)則可以推出hb(A,C)) ;
? ? ? ? 7.中斷(一個線程被其它線程interrupt,那么檢測中斷(IsInterrupted)或者拋出?
? ? ? ? ? ?InterruptedExcption一定能被其它線程看見);
? ? ? ? 8.構造方法(對象構造方法的最后一條指令,finalize()方法一定能看到)
? ? ? ? 9.工具類的happens-before原則
? ? ? ? ? ? ? ?9.1.線程安全的容器,如“ConcurrentHashMap”,get一定能看到之前的所有put操作;
? ? ? ? ? ? ? ? 9.2.CountDownLatch
? ? ? ? ? ? ? ? 9.3.Semaphore
? ? ? ? ? ? ? ? 9.4.Future
? ? ? ? ? ? ? ? 9.5.線程池
? ? ? ? ? ? ? ? 9.6.CyclicBarrier
1.3.3.3.原子性
1.3.3.3.1.什么是原子性
? ? ?一系列操作,要么全部執行成功,要么全部不執行或全部執行失敗,不會出現執行一半的情況,原子是不可分割的;
1.3.3.3.2.Java中的原子操作有哪些
1.除long和double之外的基本類型賦值操作(int,byte,boolean,short,char,float);
2.所有“引用”的賦值操作;
3.java.concurrent.Atomic.*包下所有類的原子操作;
備注:創建對象不是原子性操作!
1.創建對象操作不是原子性操作,其由三個操作構成(Singleton5 instance = new Singleton5(););1.1.創建一個空的instance;1.2.調用構造方法;1.3.將創建好的對象賦值給instance實例;
1.3.3.3.3.long和double原子性問題
? ? ? ? 對于32位的JVM long和double的操作不是原子的(32位JVM中會將long和double的一次寫入操作拆分成2個單獨的寫入操作),但是在64位的虛擬機上long和double的操作是原子的,在實際開發中商用的Java虛擬機已經處理了這個問題;我們自己也可以使用volatile去解決;
1.3.3.4.原子操作 + 原子操作 != 原子操作
? ? ? ? 簡單地把原子操作組合在一起并不能保證整體依然具有原子性;
1.4.synchronized可見性的正確理解
????????1.4.1.synchronized不僅保證了原子性還保證了可見性;
????????1.4.2.synchronized不僅讓被保護的代碼線程安全,還讓加鎖之前的代碼具有可見性;
1.5.面試題
1.5.1.單例模式的七種寫法及單例和并發的關系
? ? ? ? 詳見:單例模式的七種寫法URL
1.5.2.講一講什么是Java內存模型
? ? ? ? 是一組規范,規范了JVM,CPU,JAVA代碼之間一系列轉換關系,Java內存模型最重要是“重排序”,“可見性”,“原子性”這三個部分(重排序講1.3.3.1重排序的例子和重排序的好處),(可見性講因為CPU有多級緩存JMM對內存抽象為“主內存”和“本地內存”,主內存是所有線程所共享的,本地內存是線程獨占的其它線程訪問不了。一個線程對變量的更改是先更新到本地內存中再同步到主內存中,其它線程只能在主內存中同步這個變量的值,因為本地內存同步到主內存是需要時間的這樣就會導致一個線程在本地內存中已經更改了值而這個值還沒有被同步到主內存中去,這樣對于其它線程來說這個值的更改是不可見的,就會導致其它線程重主內存中拿到的值還是一個舊的值,這樣就出現了“線程安全”問題,對于“可見性”來說還有一個happens-before原則即在時間上動作A發生在動作B之前,B保證能看見A的所有操作這就是happens-before;?再可以講下1.3.2.2.4哪些運用了happens-before原則及再講下volatie關鍵字volatile關鍵字),(最后可以再講下1.3.3.3.2Java中的原子操作有哪些);
1.5.3.什么是原子操作?Java中有哪些原子操作?創建對象是原子操作嗎?
? ? ? ? 詳見1.3.33原子性;
1.5.4.64位的double和long寫入的時候是原子的嗎?
? ? ? ? 32位虛擬機上不是原子的,64位虛擬機上是原子的,實際開發中使用的商用Java虛擬機已經處理了這個問題不需要我們再考慮;
1.5.5.volatile和synchronized的異同
? ? ? ? 詳見volatile關鍵字;