目錄
一、什麼是JMM
二、JMM定義了什麼
原子性
可見性
有序性
三、八種記憶體互動操作
四、volatile关键字
可见性
volatile一定能保证线程安全吗
禁止指令重排序
volatile禁止指令重排序的原理
首頁 Java java教程 Java之JMM高並發程式設計實例分析

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

May 02, 2023 pm 06:52 PM
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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1273
29
C# 教程
1252
24
突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP的影響:網絡開發及以後 PHP的影響:網絡開發及以後 Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP:許多網站的基礎 PHP:許多網站的基礎 Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

PHP與Python:用例和應用程序 PHP與Python:用例和應用程序 Apr 17, 2025 am 12:23 AM

PHP適用於Web開發和內容管理系統,Python適合數據科學、機器學習和自動化腳本。 1.PHP在構建快速、可擴展的網站和應用程序方面表現出色,常用於WordPress等CMS。 2.Python在數據科學和機器學習領域表現卓越,擁有豐富的庫如NumPy和TensorFlow。

See all articles