搜尋
首頁Javajava教程Java之JMM高並發程式設計實例分析

一、什麼是JMM

JMM就是Java記憶體模型(java memory model)。因為在不同的硬體生產商和不同的作業系統下,記憶體的存取有一定的差異,所以會造成相同的程式碼運作在不同的系統上會出現各種問題。所以java記憶體模型(JMM)屏蔽掉各種硬體和作業系統的記憶體存取差異,以實現讓java程式在各種平台下都能達到一致的並發效果。

Java記憶體模型規定所有的變數都儲存在主記憶體中,包括實例變量,靜態變量,但不包含局部變數和方法參數。每個線程都有自己的工作內存,線程的工作內存保存了該線程用到的變量和主內存的副本拷貝,線程對變量的操作都在工作內存中進行。線程不能直接讀寫主記憶體中的變數。

不同的執行緒之間也無法存取對方工作記憶體中的變數。線程之間變數值的傳遞均需要透過主記憶體來完成。

Java之JMM高並發程式設計實例分析

每個執行緒的工作記憶體都是獨立的,執行緒操作資料只能在工作記憶體中進行,然後刷回到主記憶體。這是 Java 記憶體模型定義的線程基本工作方式。

溫馨提醒一下,這裡有些人會把Java記憶體模型誤解為Java記憶體結構,然後答到堆,棧,GC垃圾回收,最後和麵試官想問的問題相差甚遠。其實一般問到Java記憶體模型都是想問多線程,Java並發相關的問題。

二、JMM定義了什麼

這個簡單,整個Java記憶體模型其實是圍繞著三個特徵建立起來的。分別是:原子性,可見性,有序性。這三個特徵可謂是整個Java並發的基礎。

原子性

原子性指的是一個操作是不可分割,不可中斷的,一個執行緒在執行時不會被其他執行緒幹擾。

面試官拿筆寫了段程式碼,下面這幾句程式碼能保證原子性嗎?

int i = 2;
int j = i;
i++;
i = i + 1;

第一句是基本型別賦值運算,必定是原子性運算。

第二句先讀取i的值,再賦值到j,兩步驟操作,不能保證原子性。

第三和第四句其實是等效的,先讀取i的值,再 1,最後賦值到i,三步操作了,不能保證原子性。

JMM只能保證基本的原子性,如果要保證一個程式碼區塊的原子性,提供了monitorenter 和 moniterexit 兩個字節碼指令,也就是 synchronized 關鍵字。因此在 synchronized 區塊之間的操作都是原子性的。

可見性

可見性指當一個執行緒修改共享變數的值,其他執行緒能夠立即知道被修改了。 Java是利用volatile關鍵字來提供可見性的。當變數被volatile修飾時,這個變數被修改後會立刻刷新到主內存,當其它線程需要讀取該變數時,就會去主內存中讀取新值。而普通變數則不能保證這一點。

除了volatile關鍵字之外,final和synchronized也能實現可見性。

synchronized的原理是,在執行完,進入unlock之前,必須將共享變數同步到主記憶體中。

final修飾的字段,一旦初始化完成,如果沒有物件逸出(指物件為初始化完成就可以被別的執行緒使用),那麼對於其他執行緒都是可見的。

有序性

在Java中,可以使用synchronized或volatile保證多執行緒之間操作的有序性。實作原理有些差異:

volatile關鍵字是使用記憶體屏障達到禁止指令重排序,以確保有序性。

synchronized的原理是,一個執行緒lock之後,必須unlock後,其他執行緒才可以重新lock,使得被synchronized包住的程式碼區塊在多執行緒之間是串列執行的。

三、八種記憶體互動操作

記憶體互動操作有8種:

  • lock(鎖定),作用於主記憶體中的變量,把變數標識為執行緒獨佔的狀態。

  • read(讀取),作用於主記憶體的變量,把變數的值從主記憶體傳送到執行緒的工作記憶體中,以便下一步的load操作使用。

  • load(已載入),作用於工作記憶體的變量,把read操作主存的變數放入工作記憶體的變數副本中。

  • use(使用),作用於工作記憶體的變量,把工作記憶體中的變數傳送到執行引擎,每當虛擬機器遇到一個需要使用到變數的值的位元組碼指令時將會執行這個操作。

  • assign(賦值),作用於工作記憶體的變量,它把一個從執行引擎中接受到的值賦值給工作記憶體的變數副本中,每當虛擬機器遇到一個給變數賦值的字節碼指令時將會執行這個操作。

  • store(儲存),作用於工作記憶體的變量,它把一個從工作記憶體中一個變數的值傳送到主記憶體中,以便後續的write使用。

  • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

我再补充一下JMM对8种内存交互操作制定的规则吧:

  • 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。

  • 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。

  • 不允许线程将没有assign的数据从工作内存同步到主内存。

  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。

  • 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。

  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。

  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。

  • 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。

四、volatile关键字

很多并发编程都使用了volatile关键字,主要的作用包括两点:

  • 保证线程间变量的可见性。

  • 禁止CPU进行指令重排序。

可见性

volatile修饰的变量,当一个线程改变了该变量的值,其他线程是立即可见的。普通变量则需要重新读取才能获得最新值。

volatile保证可见性的流程大概就是这个一个过程:

Java之JMM高並發程式設計實例分析

volatile一定能保证线程安全吗

先说结论吧,volatile不能一定能保证线程安全。

怎么证明呢,我们看下面一段代码的运行结果就知道了:

public class VolatileTest extends Thread {
private static volatile int count = 0;
public static void main(String[] args) throws Exception {
Vector<Thread> threads = new Vector<>();
for (int i = 0; i < 100; i++) {
VolatileTest thread = new VolatileTest();
threads.add(thread);
thread.start();
}
//等待子线程全部完成
for (Thread thread : threads) {
thread.join();
}
//输出结果,正确结果应该是1000,实际却是984
System.out.println(count);//984
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
//休眠500毫秒
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
count++;
}
}
}

为什么volatile不能保证线程安全?

很简单呀,可见性不能保证操作的原子性,前面说过了count++不是原子性操作,会当做三步,先读取count的值,然后+1,最后赋值回去count变量。需要保证线程安全的话,需要使用synchronized关键字或者lock锁,给count++这段代码上锁:

private static synchronized void add() {
count++;
}

禁止指令重排序

首先要讲一下as-if-serial语义,不管怎么重排序,(单线程)程序的执行结果不能被改变。

为了使指令更加符合CPU的执行特性,最大限度的发挥机器的性能,提高程序的执行效率,只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码逻辑顺序不一致,这个过程就叫做指令的重排序。

重排序的种类分为三种,分别是:编译器重排序,指令级并行的重排序,内存系统重排序。整个过程如下所示:

Java之JMM高並發程式設計實例分析

指令重排序在单线程是没有问题的,不会影响执行结果,而且还提高了性能。但是在多线程的环境下就不能保证一定不会影响执行结果了。

所以在多线程环境下,就需要禁止指令重排序。

volatile关键字禁止指令重排序有两层意思:

  • 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。

  • 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

下面举个例子:

private static int a;//非volatile修饰变量
private static int b;//非volatile修饰变量
private static volatile int k;//volatile修饰变量
private void hello() {
a = 1; //语句1
b = 2; //语句2
k = 3; //语句3
a = 4; //语句4
b = 5; //语句5
//...
}

变量a,b是非volatile修饰的变量,k则使用volatile修饰。所以语句3不能放在语句1、2前,也不能放在语句4、5后。但是语句1、2的顺序是不能保证的,同理,语句4、5也不能保证顺序。

并且,执行到语句3的时候,语句1,2是肯定执行完毕的,而且语句1,2的执行结果对于语句3,4,5是可见的。

volatile禁止指令重排序的原理

首先要讲一下内存屏障,内存屏障可以分为以下几类:

  • LoadLoad 屏障:對於這樣的語句Load1,LoadLoad,Load2。在Load2及後續讀取操作要讀取的資料被存取前,保證Load1要讀取的資料已讀取完畢。

  • StoreStore屏障:對於這樣的語句Store1, StoreStore, Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。

  • LoadStore 屏障:對於這樣的語句Load1, LoadStore,Store2,在Store2及後續寫入作業被刷出前,保證Load1要讀取的資料被讀取完畢。

  • StoreLoad 屏障:對於這樣的語句Store1, StoreLoad,Load2,在Load2及後續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。

在每個volatile讀取作業後插入LoadLoad屏障,在讀取作業後插入LoadStore屏障。

Java之JMM高並發程式設計實例分析

在每個volatile寫入作業的前面插入一個StoreStore屏障,後面插入一個SotreLoad屏障。

Java之JMM高並發程式設計實例分析

以上是Java之JMM高並發程式設計實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:亿速云。如有侵權,請聯絡admin@php.cn刪除
如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境