在多線程編程中,就涉及到線程之間的通信。爲了更好的實現程序的高併發、高性能、高可用,就不得不知道JMM。至於高可用,今後再詳細總結。另外,此處有一文介紹HA,參考https://www.linuxprobe.com/high-availability.html
一、JMM
1、JMM定義了Java 虛擬機(JVM)在計算機內存(RAM)中的工作方式。JVM是java整個計算虛擬模型。
2、從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。
3、本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩衝區、寄存器以及其他的硬件和編譯器優化。
二、JVM對Java內存模型的實現
根據JMM模型,在JVM把內存分成了兩部分:線程棧區和堆區。
JVM中運行的每個線程都擁有自己的線程棧(也稱調用棧),線程棧包含了當前線程執行的方法調用相關信息。隨着代碼的不斷執行,調用棧會不斷變化。
1、線程棧區(Thread stack)
1)線程棧(
thread stack,
也叫線程堆棧),所擁有的資源是獨自的,對其他線程不可見。2)線程棧,也包含正在的所有局部變量。
3)由當前線程創建的局部變量,對於非創建它的其他所有線程都是不可見的。
4)即使兩個線程正在執行完全相同的代碼,兩個線程仍然會在每個線程堆棧中創建該代碼的局部變量。即便可傳遞變量副本,但不共享原始局部變量本身。
2、堆區(Heap)
1)堆包含創建的java應用程序對象。
2)堆中的對象可以被具有對象引用的所有線程訪問。當一個線程訪問一個對象時,它也可以訪問該對象的成員變量。
3)如果兩個線程同時調用同一個對象上的一個方法,它們都可以訪問該對象的成員變量,但每個線程都有自己的局部變量副本
4)堆中的數據是共享的,線程不安全的
詳細說明:
- 所有原始類型(boolean,byte,short,char,int,long,float,double)的局部變量都直接保存在線程棧當中,對於它們的值各個線程之間都是獨立的。對於原始類型的局部變量,一個線程可以傳遞一個變量副本給另一個線程,但原始變量是不共享的。
- 堆區包含了Java應用創建的所有對象信息(包括原始類型的封裝類),不管對象是哪個線程創建的,不管對象是屬於一個成員變量還是方法中的局部變量,它都會被存儲在堆區。
- 一個局部變量如果是原始類型,那麼它會被完全存儲到棧區。 一個局部變量也有可能是一個對象的引用,這種情況下,這個本地引用會被存儲到棧中,但是對象本身仍然存儲在堆區。
- 對於一個對象的成員方法,這些方法中包含局部變量,仍需要存儲在棧區,即使它們所屬的對象在堆區。
- 對於一個對象的成員變量,不管它是原始類型還是包裝類型,都會被存儲到堆區。
- Static類型的變量以及類本身相關信息都會隨着類本身存儲在堆區。
基於JMM的JVM模型,既然堆中的數據是共享的,那麼在多線程環境中,就可能存在數據安全性問題。主要涉及到:可見性問題,競爭性問題等。
在此之前,回顧幾個點
A、計算機常識:
- cpu執行的操作是原子性的,是不可拆分的。
B、造成數據安全性問題的必要條件:
- 多線程環境
- 多個線程操作共享數據
- 操作共享數據的語句不是原子性的(多條)
此中,引申的。後續補充
3、共享對象的可見性(事例說明)
如果兩個或多個線程共享一個對象,但沒有正確使用volatile
聲明或Synchronized
同步機制,一個線程更新了共享變量值後,對於其他線程來講是不可見的。如線程A,線程B同時要進行modify。
public class Account { private float balance; public void modify (float difference) { float value=this.balance; this.balance=value+difference; } }
首先,線程A和線程B在各自的thread stack
中維護了一分局部變量的副本,線程A修改了線程A中Thread stack中的局部變量,但是還沒有還沒將修改的數據刷新到Main Memory
中,而線程B獲取的值依然是old value
,就會出現問題
解決方案
- 使用
volatile
關鍵字 - 使用
synchronized
同步機制
tips:volatile與synchronized的區別:
- volatile本質是在告訴jvm當前變量在寄存器中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住
- volatile僅修飾變量;synchronized則可以修飾變量、方法、代碼塊
- volatile僅保證可見性;synchronized則可以保證可見性和原子性
- volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞
- volatile修飾的變量會禁止指令重排序,因而程序不會被編譯器優化;synchronized修飾的變量沒有禁止指令重排序,因而程序可以被編譯器優化
三、JVM
- 程序計數器(PC)
- java虛擬機棧
- 本地方法棧
- java堆
- 方法區
1、程序計數器(PC)
是一塊很小的內存空間,用於記錄將要運行的指令。
tips:每個線程都需要一個程序計數器;各個線程的計數器相互獨立,是私有的。
2、java虛擬機棧
保存了局部變量、部分結果,並參與方法的調用和返回,的一個內存空間
tips:1)對各個線程來說,也是私有的;
2)它和java線程同一時間創建;
3)由java語言實現的
3、本地方法棧
與java虛擬機棧的功能相似
tips:1)java虛擬機棧用於管理Java函數的調用,本地方法棧用於管理本地方法的調用;
2)由C語言實現的
4、java堆
tips:1)做存儲的,爲所有創建的對象和數組分配內存空間;
2)被JVM中所有的線程共享
5、方法區
也被稱爲永久區,與堆空間相似,被JVM中所有的線程共享。
1)方法區主要保存的信息是類的元數據,方法區中最爲重要的是類的類型信息、常量池、域信息、方法信息。其中運行時常量池就在方法區;
2)GC對永久區的回收:一是對永久區常量池的回收;二是永久區對元數據的回收
參考鳴謝: