自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文帶你深入理解JVM內(nèi)存模型

云計算 虛擬化
在共享內(nèi)存的并發(fā)模型里面,線程之間共享程序的公共狀態(tài),線程之間通過讀寫內(nèi)存中公共狀態(tài)來進(jìn)行隱式通信。

[[422709]]

 一、JAVA的并發(fā)模型

共享內(nèi)存模型

在共享內(nèi)存的并發(fā)模型里面,線程之間共享程序的公共狀態(tài),線程之間通過讀寫內(nèi)存中公共狀態(tài)來進(jìn)行隱式通信

該內(nèi)存指的是主內(nèi)存,實(shí)際上是物理內(nèi)存的一小部分

二、JAVA 內(nèi)存模型的抽象

1、java內(nèi)存中哪些數(shù)據(jù)是線程安全的,哪些是非安全的

非線程安全:

在java中所有的實(shí)例域、靜態(tài)域、和數(shù)組元素都存放在堆內(nèi)存中,并且這些數(shù)據(jù)是線程共享的,所以會存在內(nèi)存可見性問題

線程安全

局部變量、方法定義的參數(shù)、異常處理器參數(shù)是當(dāng)前線程的虛擬機(jī)棧中的數(shù)據(jù),并且不會進(jìn)行線程共享,所以不會存在內(nèi)存可見性問題

2、線程間通訊的本質(zhì)

線程間通訊的本質(zhì)是

JMM即JAVA內(nèi)存模型進(jìn)行控制,JMM決定了一個線程對共享變量的寫入何時對其他線程可見。

由上圖能看出來線程間的通訊都是通過主內(nèi)存來進(jìn)行傳遞消息的, 每個線程在進(jìn)行共享數(shù)據(jù)處理的時候都是將共享的數(shù)據(jù)復(fù)制到當(dāng)前線程本地(每個線程自己都有一個內(nèi)存)來進(jìn)行操作。

消息通訊過程(不考慮數(shù)據(jù)安全性的問題)

線程一將主內(nèi)存中的共享變量 A 加載到自己的本地內(nèi)存中進(jìn)行處理。比如 A = 1; 此時將修改的共享變量 A 刷入到主內(nèi)存中, 之后線程二再將主內(nèi)存中的共享變量 A 讀取到本地內(nèi)存進(jìn)行操作; 整個數(shù)據(jù)交互的過程是JMM控制的,主要控制主內(nèi)存與每個線程的本地內(nèi)存如何進(jìn)行交互來提供共享數(shù)據(jù)的可見性

三、重排序

程序在執(zhí)行的時候?yàn)榱颂岣咝蕰⒊绦蛑噶钸M(jìn)行重新排序

1、重排序分類

編譯器優(yōu)化重排序

編譯器在不改變單線程程序語義的情況下進(jìn)行語句執(zhí)行順序的優(yōu)化

指令集并行重排序

如果不存在數(shù)據(jù)的依賴性的話,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序

內(nèi)存系統(tǒng)重排序

由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行

2、重排序過程

以上三種重排序都會導(dǎo)致我們在寫并發(fā)程序的時候出現(xiàn)內(nèi)存可見性的問題。

JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序;

JMM的處理器重排序規(guī)則會要求java編譯器在生成指令序列的時候插入特定的內(nèi)存屏障指令,通過內(nèi)存屏障指令來禁止特定類型的處理器進(jìn)行重排序

3、處理器重排序

由于為了避免處理器等待向內(nèi)存中寫入數(shù)據(jù)的延時,在處理器和內(nèi)存中間加了一個緩沖區(qū),這樣處理器可以一直向緩沖區(qū)中寫入數(shù)據(jù),等到一定時間將緩沖區(qū)的數(shù)據(jù)一次性的刷入到內(nèi)存中。

優(yōu)點(diǎn):

1.處理器不同停頓,提高了處理器的運(yùn)行效率

2.減少在向內(nèi)存寫入數(shù)據(jù)時的內(nèi)存總線的占用

缺點(diǎn):

每個處理器上的寫緩沖區(qū)只對當(dāng)前處理器可見,所以就會造成內(nèi)存操作的執(zhí)行順序和實(shí)際情況不符合 例如以下場景 :

在當(dāng)前場景中就可能出現(xiàn)在處理器A和處理器B沒有將它們各自的寫緩沖區(qū)中的數(shù)據(jù)刷回內(nèi)存中, 將內(nèi)存中讀取的A=0、B =0進(jìn)行給X和Y賦值,此時將緩沖區(qū)的數(shù)據(jù)刷入內(nèi)存,導(dǎo)致了最后結(jié)果和實(shí)際想要的結(jié)果不一致。因?yàn)橹挥袑⒕彌_區(qū)的數(shù)據(jù)刷入到了內(nèi)存中才叫真正的執(zhí)行

以上主內(nèi)存與工作內(nèi)存之間的具體交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存,如何從工作內(nèi)存同步到主內(nèi)存之間的實(shí)現(xiàn)細(xì)節(jié),JMM定義了以下8種操作來完成

如果要把一個變量從主內(nèi)存中復(fù)制到工作內(nèi)存中,就需要按順序地執(zhí)行read和load操作,如果把變量從工作內(nèi)存中同步到主內(nèi)存中,就需要按順序地執(zhí)行store和write操作。但Java內(nèi)存模型只要求上述操作必須按順序執(zhí)行,而沒有保證必須是連續(xù)執(zhí)行

操作執(zhí)行流程圖解:

同步規(guī)則分析

  • 不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存中
  • 一個新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個未被初始化(load或者assign)的變量。即就是對一個變量實(shí)施use和store操作之前,必須先自行assign和load操作。
  • 一個變量在同一時刻只允許一條線程對其進(jìn)行l(wèi)ock操作,但lock操作可以被同一線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會被解鎖。lock和unlock必須成對出現(xiàn)。
  • 如果對一個變量執(zhí)行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量之前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。
  • 如果一個變量事先沒有被lock操作鎖定,則不允許對它執(zhí)行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。
  • 對一個變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)

4、內(nèi)存屏障指令

為了解決處理器重排序?qū)е碌膬?nèi)存錯誤,java編譯器在生成指令序列的適當(dāng)位置插入內(nèi)存屏障指令,來禁止特定類型的處理器重排序

內(nèi)存屏障指令

5、happens-before(先行規(guī)則)

happens-before 原則來輔助保證程序執(zhí)行的原子性、可見性以及有序性的問題,它是判斷數(shù)據(jù)是否存在競爭、線程是否安全的依據(jù)

在JMM中如果一個操作中的結(jié)果需要對另一個操作可見,那么這兩個操作之前必須要存在happens-before關(guān)系 (兩個操作可以是同一個線程也可以不是一個線程)

規(guī)則內(nèi)容:

程序順序規(guī)則

指的是在一個線程內(nèi)控制代碼順序,比如分支、循環(huán)等,即在一個線程內(nèi)必須保證語義串行性,也就是說按照代碼順序執(zhí)行

加鎖規(guī)則

一個解鎖(unlock)操作一定要發(fā)生于一個加鎖(lock)操作之前,也就是說,如果對于一個鎖解鎖后,再加鎖,那么加鎖的動作必須在解鎖動作之后(同一個鎖)

volatile變量規(guī)則

對一個volatile的變量的寫操作要發(fā)生在對這個變量的讀操作之前,這保證了volatile變量的可見性,簡單的理解就是,volatile變量在每次被線程訪問時,都強(qiáng)迫從主內(nèi)存中讀該變量的值,而當(dāng)該變量發(fā)生變化時,又會強(qiáng)迫將最新的值刷新到主內(nèi)存,任何時刻,不同的線程總是能夠看到該變量的最新值

線程啟動規(guī)則

線程的啟動方法 start() 要發(fā)生在當(dāng)前線程所有操作之前

線程終止規(guī)則

線程中所有的操作都要發(fā)生在線程終止之前,Thread.join()方法的作用是等待當(dāng)前執(zhí)行的線程終止。假設(shè)在線程B終止之前,修改了共享變量,線程A從線程B的join方法成功返回后,線程B對共享變量的修改將對線程A可見

線程中斷規(guī)則

線程調(diào)用interrupt()方法要發(fā)生在被中斷線程的代碼檢查出中斷事件之前

對象終結(jié)規(guī)則

對象的初始化完成要發(fā)生在對象被回收之前

傳遞性規(guī)則

如果操作A發(fā)生在操作B之前,操作B又發(fā)生在操作C之前,那么操作A一定發(fā)生于操作C之前

注意:兩個操作之間具有 happens-before 關(guān)系,并不意味著前一個操作必須要在后一個操作之前執(zhí)行,只需要前一個操作的結(jié)果對后一個操作可見,并且前一個操作按順序要排在后一個操作之前。

6、數(shù)據(jù)依賴性

就是前一個操作的結(jié)果對后一個操作的結(jié)果產(chǎn)生影響,此時編譯器和處理器在處理當(dāng)前有數(shù)據(jù)依賴性的操作時不會改變存在數(shù)據(jù)依賴的兩個操作的執(zhí)行順序

注意: 此時所說的數(shù)據(jù)依賴僅僅針對單個處理器中執(zhí)行的指令序列或者單個線程中執(zhí)行的操作。不同處理器和不同線程的情況編譯器和處理器是不會考慮的

7、as-if-serial

在單線程情況下不管怎么重排序程序的執(zhí)行結(jié)果不能被改變,所以如果在單處理器或者單線程的情況下,編譯器和處理器對于有數(shù)據(jù)依賴性的操作是不會進(jìn)行重排序的。反之如果沒有數(shù)據(jù)依賴性的操作就有可能發(fā)生指令重排。

四、數(shù)據(jù)競爭與順序一致性

在多線程情況下才會出現(xiàn)數(shù)據(jù)競爭

1、數(shù)據(jù)競爭

在一個線程中寫了一個變量,在另一個線程中讀一個變量,而且寫和讀并沒有進(jìn)行同步

2、順序一致性

如果在多線程條件下,程序能夠正確地使用同步機(jī)制,那么程序的執(zhí)行將具有順序一致性(就像在單線程條件下執(zhí)行一樣) 程序最終運(yùn)行的結(jié)果與你預(yù)期的結(jié)果一樣

3、順序一致性內(nèi)存模型

5.3.1特性:

一個線程中的所有操作必須按照程序的順序來執(zhí)行 所有的操作都必須是原子性的操作,并且對其他線程可見的

5.3.2概念:

在概念上,順序一致性有一個單一的全局內(nèi)存,在任意時間點(diǎn)最多只有一個線程可以連接到內(nèi)存,當(dāng)在多線程的場景下,會把所有內(nèi)存的讀寫操作變成串行化

5.3.3案例:

例如有多個并發(fā)線程A B C, A 線程有兩個操作A1 A2, 他們的執(zhí)行的順序是 A1->A2 。B 線程有三個操作B1 B2 B3, 他們的執(zhí)行的順序是B1->B2->B3 。C線程有兩個操作C1 C2那么他們在程序中執(zhí)行的順序是C1->C2 。

場景分析:

場景一: 并發(fā)安全(同步)執(zhí)行順序

A1->A2->B1->B2->B3->C1->C2

場景二: 并發(fā)不安全(非同步)執(zhí)行順序

A1->B1->A2->C1->B2->B3->C2

結(jié)論:

在非同步的場景下,即使三個線程中的每一個操作亂序執(zhí)行,但是在每個線程中的各自操作還是保持有序的。并且所有線程都只能看到一個一致的整體執(zhí)行順序,也就是說三個線程看到的都是該順序 : A1->B1->A2->C1->B2->B3->C2 ,因?yàn)轫樞蛞恢滦詢?nèi)存模型中的每個操作必須立即對任意線程可見。

以上案例場景在JMM中不是這樣的,未同步的程序在JMM中不僅整體的執(zhí)行順序變了,就連每個線程的看到的操作執(zhí)行順序也是不一樣的。

例如前面所說的如果線程A將變量的值a=2寫入到了自己的本地內(nèi)存中,還沒有刷入到主存中,在線程 A 來看值是變了,但是其他線程B線程C根本看不到值得改變,就認(rèn)為線程A的操作還沒有發(fā)生,只有線程A將工作內(nèi)存中的值刷回主內(nèi)存線程B和線程C才能的到。但是如果是同步的情況下,順序一致性模型和JMM模型執(zhí)行的結(jié)果是一致的,但是程序的執(zhí)行順序不一定,因?yàn)樵贘MM中,會發(fā)生指令重排現(xiàn)象所以執(zhí)行順序會不一致。

 

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2019-10-11 08:41:35

JVM虛擬機(jī)語言

2023-11-05 12:05:35

JVM內(nèi)存

2020-03-18 13:40:03

Spring事數(shù)據(jù)庫代碼

2019-12-06 09:44:27

HTTP數(shù)據(jù)安全

2023-10-27 07:47:58

Java語言順序性

2021-11-26 00:00:48

JVM內(nèi)存區(qū)域

2021-01-27 11:10:49

JVM性能調(diào)優(yōu)

2015-12-28 11:41:57

JVM內(nèi)存區(qū)域內(nèi)存溢出

2023-12-26 08:08:02

Spring事務(wù)MySQL

2020-11-27 08:02:41

Promise

2022-11-21 09:09:08

Linux物理內(nèi)存管理

2015-03-24 13:28:52

Java Java Strin內(nèi)存模型

2022-06-22 08:02:11

CPU操作系統(tǒng)Java

2024-05-24 14:35:49

2022-10-24 08:48:07

虛擬內(nèi)存Linux

2018-01-22 17:02:48

Python字符編碼ASCII

2021-10-13 21:43:18

JVMRPC框架

2023-03-31 08:16:53

Flutter優(yōu)化內(nèi)存管理

2021-06-06 13:06:34

JVM內(nèi)存分布

2017-11-20 11:05:23

數(shù)據(jù)庫MongoDB索引
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號