日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

java线程内存模型_深度解析Java多线程的内存模型

發(fā)布時(shí)間:2024/2/28 java 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java线程内存模型_深度解析Java多线程的内存模型 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

內(nèi)部java內(nèi)存模型

硬件層面的內(nèi)存模型

Java內(nèi)存模型和硬件內(nèi)存模型的聯(lián)系

共享對(duì)象的可見(jiàn)性

資源競(jìng)速

Java內(nèi)存模型很好的說(shuō)明了JVM是如何在內(nèi)存里工作的,JVM可以理解為java執(zhí)行的一個(gè)操作系統(tǒng),作為一個(gè)操作系統(tǒng)就有內(nèi)存模型,這就是我們常說(shuō)的JAVA內(nèi)存模型。

如果我們想正確的寫(xiě)多線程的并行程序。理解好java內(nèi)存模型在多線程下的工作方式是及其重要的,這可以幫我們更好的理解底層的工作方式。

java內(nèi)存模型說(shuō)明了不同的線程怎樣以及何時(shí)可以看到其他線程寫(xiě)入共享變量的值,以及同步程序怎么共享變量。最初的java內(nèi)存模型不夠好,存在很多的不足,所以在java1.5z中,java內(nèi)存模型的版本的進(jìn)行了一次重大的更新與改進(jìn),并且在java8中仍然被使用。

內(nèi)部java內(nèi)存模型

JVM的內(nèi)部的內(nèi)存模型分為了兩部分,thread stack和heap,也就是線程棧和堆,我們將復(fù)雜的內(nèi)存模型抽象成下圖:

Paste_Image.png

每一個(gè)在JVM中運(yùn)行的線程在內(nèi)存里都會(huì)有屬于自己的線程棧。線程棧一般包含這個(gè)線程的方法執(zhí)行到哪一個(gè)點(diǎn)了這些信息,也被稱作“call stack”,當(dāng)線程執(zhí)行代碼,調(diào)用棧就會(huì)隨著執(zhí)行的狀態(tài)改變。

線程棧也包括了每個(gè)方法執(zhí)行時(shí)的local 變量,所有的方法也都存儲(chǔ)在線程棧上,一個(gè)線程可以只能訪問(wèn)自己的線程棧。每個(gè)線程自己創(chuàng)建的本地本地變量對(duì)其他線程是不可見(jiàn)的,也就是私有的,即使兩個(gè)線程調(diào)用的是同一個(gè)方法,每個(gè)線程會(huì)分別保存一份本地變量,各自屬于各自的線程棧。

所有基本類型的local變量( boolean, byte, short, char, int, long, float, double)全都被存儲(chǔ)在線程棧里,而且對(duì)其他線程是不可見(jiàn)的,一個(gè)線程可能會(huì)傳遞一份基本類型的變量值的一份拷貝給另一個(gè)線程,但是自己本身的變量是不能共享的,只能傳遞拷貝。

堆中存儲(chǔ)著java程序中new出來(lái)的對(duì)象,不管是哪個(gè)線程new出來(lái)的對(duì)象,都存在一起,而且不區(qū)分是哪個(gè)線程的對(duì)象。這些對(duì)象里面也包括那些原始類型的對(duì)象版本(e.g. Byte, Integer, Long etc.). 不管這個(gè)對(duì)象是分配給本地變量還是成員變量,最終都是存在堆里。

下面這個(gè)圖就說(shuō)明了線程棧中存儲(chǔ)了local變量,堆中存儲(chǔ)著對(duì)象object。

Paste_Image.png

一個(gè)原始數(shù)據(jù)類型的本地變量將完全被存儲(chǔ)在線程棧中。

本地變量也可以是指向?qū)ο蟮囊?#xff0c;在這種情況下,本地變量存在線程棧上,但是對(duì)象本身是存在堆上。

一個(gè)對(duì)象可能包含方法這些方法同時(shí)也會(huì)包含本地變量,這些本地變量也是存儲(chǔ)在線程棧上面,即使他們所屬于的對(duì)象和方法是存在堆上的。

一個(gè)對(duì)象的成員變量是跟隨著對(duì)象本身存儲(chǔ)在堆上的,不管成員變量是原始數(shù)據(jù)類型還是指向?qū)ο蟮囊谩?/p>

靜態(tài)的類變量一般也存儲(chǔ)在堆上,根據(jù)類的定義。

存儲(chǔ)在堆上的對(duì)象可以被所有的線程通過(guò)引用來(lái)訪問(wèn)。當(dāng)一個(gè)線程持有一個(gè)對(duì)象的引用時(shí),他同時(shí)也就可以訪問(wèn)這個(gè)對(duì)象的成員變量了。如果兩個(gè)線程同時(shí)調(diào)用同一個(gè)對(duì)象的一個(gè)方法,他們就會(huì)都擁有這個(gè)對(duì)象的成員變量,但是每一個(gè)線程會(huì)享有自己私有的本地變量。

下面這張圖就說(shuō)明以上的內(nèi)容

Paste_Image.png

兩個(gè)線程有一系列的本地變量。其中一個(gè)本地變量(Local Variable 2)指向堆中的object3.這兩個(gè)線程每個(gè)都有指向同一個(gè)對(duì)象object3的不同引用。他們的引用是本地變量,都存在各自的線程棧中,雖然這兩個(gè)不同的引用是指向同一個(gè)對(duì)象的。

我們還可以發(fā)現(xiàn),共有的對(duì)象object3有指向object2和object4的引用,這些引用是作為object3中的成員變量存在的。通過(guò)object3中的成員變量的引用,兩個(gè)線程都可以訪問(wèn)到object2和object4.

這個(gè)圖也說(shuō)明了指向堆中不同對(duì)象的本地變量。例如圖中的object1和object5,不是同一個(gè)對(duì)象。理論上,所有的線程都可以訪問(wèn)堆中的對(duì)象,只要這個(gè)線程持有堆中對(duì)象的引用。但是這個(gè)圖中,每個(gè)線程只有這兩個(gè)對(duì)象中的一個(gè)引用。

下面,我們將寫(xiě)一段實(shí)際的代碼,這段代碼的內(nèi)存模型就跟上圖一樣:

public class MyRunnable implements Runnable(){

public void run() {

methodOne();

}

public void methodOne() {

int localVariable1 = 45;

MySharedObject localVariable2 =

MySharedObject.sharedInstance;

//... do more with local variables.

methodTwo();

}

public void methodTwo() {

Integer localVariable1 = new Integer(99);

//... do more with local variable.

}

}

public class MySharedObject{

//static variable pointing to instance of MySharedObject

public static final MySharedObject sharedInstance =

new MySharedObject();

//member variables pointing to two objects on the heap

public Integer object2 = new Integer(22);

public Integer object4 = new Integer(44);

public long member1 = 12345;

public long member1 = 67890;

}

如果兩個(gè)線程執(zhí)行run方法那么,上圖的內(nèi)存模型就會(huì)是這段程序的執(zhí)行結(jié)果。

methodOne()聲明了一個(gè)原始數(shù)據(jù)類型的本地變量,int類型的localVariable1 ,和一個(gè)指向?qū)ο蟮囊玫谋镜刈兞?localVariable2).

每個(gè)線程執(zhí)行methodOne()的時(shí)候都會(huì)創(chuàng)建屬于自己的一份本地變量的拷貝,也就是

localVariable1 and localVariable2在他們各自的線程棧的空間中。localVariable1將會(huì)被完全對(duì)其他線程是不可見(jiàn)的,只存在與每個(gè)線程自己的線程棧空間中。一個(gè)線程不能看到其他線程對(duì)localVariable1所做的改變與操作,是不可見(jiàn)的。

每個(gè)線程執(zhí)行methodOne()方法的時(shí)候也會(huì)創(chuàng)建localVariable2的拷貝,但是不同的localVariable2的拷貝最終卻指向同一個(gè)堆上的對(duì)象。這段代碼讓localVariable2指向之前通過(guò)一個(gè)靜態(tài)變量引用的對(duì)象。靜態(tài)變量只會(huì)存在一份,不會(huì)有多余的拷貝,而且靜態(tài)變量是存在堆中的。所以,localVariable2的兩份拷貝同時(shí)指向同一個(gè)MySharedObject對(duì)象的實(shí)例,與此同時(shí),還有一個(gè)堆中的靜態(tài)變量也指向這個(gè)對(duì)象實(shí)例。這個(gè)對(duì)象就是對(duì)應(yīng)上圖中的object3.

我們發(fā)現(xiàn)MySharedObject 包含這兩個(gè)成員變量。這些成員變量跟對(duì)象一樣存儲(chǔ)在堆上。這兩個(gè)成員變量指向兩個(gè)integer對(duì)象。這兩個(gè)對(duì)象分別對(duì)應(yīng)上圖中的object2和object4.

我們發(fā)現(xiàn),methodTwo() 創(chuàng)建了一個(gè)本地變量叫做localVariable1。這個(gè)本地變量是一個(gè)對(duì)象的引用,他指向一個(gè)integer對(duì)象。這個(gè)方法將本地變量localVariable1指向一個(gè)新的值。在執(zhí)行methodTwo()的時(shí)候,每個(gè)線程都會(huì)持有一份localVariable1的拷貝。這兩個(gè)Integer對(duì)象將會(huì)被初始化在堆上,但是因?yàn)槊看螆?zhí)行這個(gè)方法的時(shí)候,這個(gè)方法都會(huì)創(chuàng)建一個(gè)新的對(duì)象,所以兩個(gè)線程會(huì)擁有獨(dú)立的對(duì)象實(shí)例。這兩個(gè)對(duì)象就對(duì)應(yīng)上圖中的object1和object5.

我們發(fā)現(xiàn)MySharedObject 中的成員變量是原始數(shù)據(jù)類型,但由于他們是成員變量,所以依舊存儲(chǔ)在堆上。只有本地變量存儲(chǔ)在線程棧中。

硬件層面的內(nèi)存模型

硬件層面的內(nèi)存內(nèi)存結(jié)構(gòu)與JVM中的內(nèi)存結(jié)構(gòu)是有不同的,對(duì)我們來(lái)說(shuō),正確理解掌握硬件層面的內(nèi)存模型是很必要的,這可以幫助我們理解java多線程的底層機(jī)制,更要了解java內(nèi)存模型如何在硬件內(nèi)存結(jié)構(gòu)上工作。這一章將講述硬件層面內(nèi)存模型,下一部分將講述java如何結(jié)合硬件工作。

下圖是一個(gè)簡(jiǎn)化的現(xiàn)代計(jì)算機(jī)硬件結(jié)構(gòu)圖:

Paste_Image.png

現(xiàn)代計(jì)算機(jī)通常會(huì)有兩個(gè)甚至更多的cpu,這些cpu可能還會(huì)有多個(gè)核心,這個(gè)意義是,擁有多個(gè)cpu的計(jì)算機(jī)可能會(huì)有多個(gè)線程在同時(shí)執(zhí)行,每個(gè)cpu都可以在任何給定的時(shí)間運(yùn)行一個(gè)線程。這就意味著如果我們的java程序是多線程的,在內(nèi)部就每個(gè)線程就會(huì)有一個(gè)cpu在同時(shí)執(zhí)行。

每個(gè)cpu都會(huì)有一系列的寄存器registers在cpu的內(nèi)存中,而且這些寄存器是很重要的。cpu在寄存器上進(jìn)行計(jì)算操作比在主內(nèi)存中進(jìn)行計(jì)算要快的多。這是因?yàn)閏pu訪問(wèn)寄存器的速度比訪問(wèn)內(nèi)存要快得多。

每個(gè)cpu也會(huì)有一個(gè)cpu的cache內(nèi)存。這是因?yàn)閏pu訪問(wèn)cache比訪問(wèn)內(nèi)存的速度要快得多,但是卻比訪問(wèn)的寄存器要慢一些,所以cache的速度是介于寄存器和內(nèi)存的。一些cpu還有多級(jí)cache,比如(Level 1 and Level 2),但是這對(duì)于我們理解java內(nèi)存模型關(guān)系不大,我們只需要cpu有三層內(nèi)存結(jié)構(gòu),寄存器-cache-內(nèi)存(RAM).

一臺(tái)計(jì)算機(jī)一般都會(huì)有主內(nèi)存也就是RAM,所有cpu都可以訪問(wèn)主內(nèi)存,主內(nèi)存的容量一般遠(yuǎn)比cache大得多。

一般的,當(dāng)cpu需要訪問(wèn)內(nèi)存的時(shí)候,他會(huì)先讀取一部分主內(nèi)存到cache中,甚至,會(huì)讀取一部分cache到內(nèi)部的寄存器中,然后再在寄存器進(jìn)行計(jì)算操作。當(dāng)cpu將計(jì)算結(jié)果寫(xiě)回內(nèi)存中時(shí),他會(huì)flush寄存器和cache中的數(shù)據(jù),然后將值寫(xiě)回至內(nèi)存中。

當(dāng)cpu要求cache去存儲(chǔ)其他內(nèi)容時(shí),也會(huì)將cache中的內(nèi)容flush到內(nèi)存中。cpu的cache可以邊寫(xiě)入一部分?jǐn)?shù)據(jù)到內(nèi)存,邊寫(xiě)入一部分到自己cache中,所以在更新數(shù)據(jù),不必要全部清空cache,可以邊讀邊寫(xiě)。一般的,cache真正更新數(shù)據(jù)是在更小的內(nèi)存塊上,叫做“cache lines”。多個(gè)“cache lines”可能正在讀取數(shù)據(jù)到cache中,而另一部分可能正在將數(shù)據(jù)寫(xiě)回到內(nèi)存中。

Java內(nèi)存模型和硬件內(nèi)存模型的聯(lián)系

上文已經(jīng)提到,java內(nèi)存模型和硬件內(nèi)存模型是不同的。硬件內(nèi)存模型不區(qū)分堆和棧。在硬件層面,所有的線程棧和堆都被存儲(chǔ)在主內(nèi)存中,一部分線程棧和堆可能有時(shí)候會(huì)出現(xiàn)在cpu cache中和cpu寄存器中。下圖可以說(shuō)明這個(gè)問(wèn)題:

Paste_Image.png

當(dāng)對(duì)象和變量被存儲(chǔ)在不同的內(nèi)存區(qū)域的時(shí)候,很多問(wèn)題就可能發(fā)生,主要有以下兩類問(wèn)題:

當(dāng)線程對(duì)一些共享數(shù)據(jù)進(jìn)行更新或者寫(xiě)操作時(shí),可見(jiàn)性的問(wèn)題

當(dāng)讀寫(xiě)共享數(shù)據(jù)產(chǎn)生資源競(jìng)速的問(wèn)題

接下來(lái)的部分就會(huì)討論這兩個(gè)問(wèn)題

共享對(duì)象的可見(jiàn)性

如果多個(gè)線程在共享一個(gè)對(duì)象,沒(méi)有正確使用volatile或者synchronize聲明,更新共享對(duì)象的時(shí)候就可能出現(xiàn)其他線程不可見(jiàn)的問(wèn)題。

我們假設(shè)共享對(duì)象初始化主內(nèi)存中。一個(gè)在cpu中運(yùn)行的線程讀取共享對(duì)象到cache中。這時(shí)候,隨著程序的執(zhí)行,可能導(dǎo)致共享對(duì)象發(fā)生一些變化。只要cpu的cache還沒(méi)有被寫(xiě)回到主內(nèi)存中,這個(gè)共享對(duì)象的變化就對(duì)其他在cpu上運(yùn)行的線程不可見(jiàn)。這種情況下,每個(gè)線程都會(huì)有持有一份自己對(duì)于共享對(duì)象的拷貝,這份拷貝存儲(chǔ)在各自的cpu的cache中,而且對(duì)于其他線程是不可見(jiàn)的。

下圖說(shuō)明了大致的情況,在左邊cpu執(zhí)行的線程將共享對(duì)象讀取到cache中,并且將他的值改變?yōu)?.這個(gè)變化對(duì)右邊的cpu的其他線程是不可見(jiàn)的,因?yàn)閷?duì)于變量count的更新還沒(méi)有被寫(xiě)回到主內(nèi)存中。

Paste_Image.png

想要解決這個(gè)共享對(duì)象可見(jiàn)性的問(wèn)題,可以使用java的volatile關(guān)鍵字(參見(jiàn)筆者的另一篇volatile的博文),這個(gè)關(guān)鍵字可以保證所給定的變量都是直接從主內(nèi)存中讀取,而且每當(dāng)更新時(shí)就立即寫(xiě)回到內(nèi)存中,所以可以保證變化是及時(shí)可見(jiàn)的。

資源競(jìng)速

如果多個(gè)線程共享一個(gè)對(duì)象,而且多個(gè)線程需要更新共享對(duì)象中的變量,那么就可能造成資源競(jìng)速的發(fā)生。

假設(shè)線程A讀取讀取一個(gè)共享對(duì)象的變量count到cpu的cache中,同時(shí),線程B也執(zhí)行同樣的步驟,但是是讀取到一個(gè)不同的CPU的cache中,現(xiàn)在線程A給count加一,線程B也做同樣的事情,現(xiàn)在這個(gè)變量被加了兩次,分別在不同的cpu的cache中。

如果這兩次遞增操作是被按順序先后執(zhí)行的,這個(gè)變量count就會(huì)被加兩次而且比最初的值加了2,寫(xiě)回到主內(nèi)存中。

然而,如果這兩個(gè)遞增操作是并發(fā)執(zhí)行的,且沒(méi)有正確的進(jìn)行同步操作,寫(xiě)回內(nèi)存的時(shí)候,更新后的值只會(huì)被加一,雖然實(shí)際上是進(jìn)行了兩次遞增操作。

下圖就說(shuō)明了程序并發(fā)執(zhí)行的時(shí)候,產(chǎn)生的資源競(jìng)速的問(wèn)題:

Paste_Image.png

想要解決這個(gè)問(wèn)題,我們可以使用java中的synchronize關(guān)鍵字。synchronize可以保證只有一個(gè)線程能進(jìn)入那些被聲明為synchronize的代碼段中。同步的線程可以保證所有同步代碼段中的變量都會(huì)從內(nèi)存中讀取,而且當(dāng)線程離開(kāi)代碼塊的時(shí)候,所有更新后的值都會(huì)被寫(xiě)回主內(nèi)存中,不管這個(gè)變量有沒(méi)有被聲明volatile。

小結(jié)

本文詳細(xì)的剖析了java內(nèi)存模型和硬件層面的內(nèi)存模型,并且分析了硬件和java是怎么在內(nèi)存模型上合作聯(lián)系的。這對(duì)于我們接下來(lái)理解java多線程的概念是及其重要的,打下了牢固的基礎(chǔ)。

總結(jié)

以上是生活随笔為你收集整理的java线程内存模型_深度解析Java多线程的内存模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。