JVM學習筆記——內存模型篇

JVM學習筆記——內存模型篇在本系列內容中我們會對JVM做一個系統的學習,本片將會介紹JVM的內存模型部分
我們會分為以下幾部分進行介紹:

  • 內存模型
  • 樂觀鎖與悲觀鎖
  • synchronized優化
內存模型這一小節我們來詳細介紹一下內存模型和內存模型的三個特性
內存模型簡介首先我們來簡單介紹一下內存模型:
  • 內存模型,全稱Java Memory Model,也就是我們常說的JMM
  • JMM中定義了一套在多線程讀寫共享數據時 , 對數據的可見性,有序性和原子性的規則和保障
內存模型之原子性我們將在下面仔細介紹原子性的特點
原子性介紹我們首先介紹一下原子性:
  • 原子性是指將一系列操作規劃為一個操作,全稱不可分離進行
原子性的注意點:
  • 我們在單線程下不會出現原子性的問題
  • 但在多線程下,每條語句的實際底層操作不止一步 , 可能就會導致操作錯誤
原子性問題我們給出一個簡單的例子來解釋原子性:
package cn.itcast.jvm.t4.avo;// 在下述操作中,我們分別創造兩個線程,分別執行i++和i--50000次 , 按正常邏輯來說結果應該為0public class Demo4_1 {static int i = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int j = 0; j < 50000; j++) {i++;}});Thread t2 = new Thread(() -> {for (int j = 0; j < 50000; j++) {i--;}});t1.start();t2.start();t1.join();t2.join();System.out.println(i);}}但我們多次運行的結果如下:
// 每次結果均不相同302-9860原子性分析首先我們分別給出i++和i--的底層操作:
// i++getstatic i// 獲取靜態變量i的值iconst_1// 準備常量1iadd// 加法putstatic i// 將修改后的值存入靜態變量i// i--getstatic i// 獲取靜態變量i的值iconst_1// 準備常量1isub// 減法putstatic i// 將修改后的值存入靜態變量i我們的原子性分為兩種情況:
  • 單線程情況下:我們的順序肯定是按照正常順序來執行
  • 多線程情況下:我們i++的操作按順序執行 , i--的操作按順序執行,但兩者操作可能會交替進行
首先我們給出單線程情況下底層代碼:
// 單線程// 假設i的初始值為0getstatic i// 線程1-獲取靜態變量i的值 線程內i=0iconst_1// 線程1-準備常量1iadd// 線程1-自增 線程內i=1putstatic i// 線程1-將修改后的值存入靜態變量i 靜態變量i=1getstatic i// 線程1-獲取靜態變量i的值 線程內i=1iconst_1// 線程1-準備常量1isub// 線程1-自減 線程內i=0putstatic i// 線程1-將修改后的值存入靜態變量i 靜態變量i=0然后我們分別給出多線程情況下多種結果的底層代碼:
// 多線程// 負數// 假設i的初始值為0getstatic i// 線程1-獲取靜態變量i的值 線程內i=0getstatic i// 線程2-獲取靜態變量i的值 線程內i=0iconst_1// 線程1-準備常量1iadd// 線程1-自增 線程內i=1putstatic i// 線程1-將修改后的值存入靜態變量i 靜態變量i=1iconst_1// 線程2-準備常量1isub// 線程2-自減 線程內i=-1putstatic i// 線程2-將修改后的值存入靜態變量i 靜態變量i=-1// 正數// 假設i的初始值為0getstatic i// 線程1-獲取靜態變量i的值 線程內i=0getstatic i// 線程2-獲取靜態變量i的值 線程內i=0iconst_1// 線程1-準備常量1iadd// 線程1-自增 線程內i=1iconst_1// 線程2-準備常量1isub// 線程2-自減 線程內i=-1putstatic i// 線程2-將修改后的值存入靜態變量i 靜態變量i=-1putstatic i// 線程1-將修改后的值存入靜態變量i 靜態變量i=1原子性實現那么我們該如何實現多線程的原子性:
  • 使用synchronized(同步關鍵字)
我們這里給出synchronized的使用方式:
synchronized( 對象 ) { // 要作為原子操作代碼}我們如果要實現之前的代碼,我們可以將代碼修改為:
package cn.itcast.jvm.t4.avo;public class Demo4_1 {// 這里的i應該被多線程共用,設為靜態變量static int i = 0;// 這里是Obj對象,我們設置它為鎖,注意兩個線程中的synchronized所對應的鎖應該是同一個對象(鎖)static Object obj = new Object();public static void main(String[] args) throws InterruptedException {// 采用synchronized設置鎖實現原子性,這樣i++操作就會完整進行Thread t1 = new Thread(() -> {synchronized (obj) {for (int j = 0; j < 50000; j++) {i++;}}});Thread t2 = new Thread(() -> {// 采用synchronized設置鎖實現原子性,這樣i--操作就會完整進行synchronized (obj) {for (int j = 0; j < 50000; j++) {i--;}}});t1.start();t2.start();t1.join();t2.join();// 我們的輸出結果自然是0了~System.out.println(i);}}

推薦閱讀