内存位置访问无效 midas.dll_java并发之内存模型
作者:killianxu
來源:https://www.cnblogs.com/killianxu/p/11665903.html
java內存模型知識導圖
一 并發問題及含義
并發編程存在原子性、可見性、有序性問題。
二 內存模型
為了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存系統中多線程程序讀寫操作行為的規范。內存模型解決并發問題
主要采用兩種方式:限制處理器優化和使用內存屏障。
順序一致性內存模型是一種理論參考模型,提供了極強的內存可見性保證,具有兩大特性:
順序一致性內存模型禁止很多處理器和編譯器重排,影響執行性能,處理器內存模型和JMM對順序一致性內存模型進行放松,執行性能:處理器內存模型>JMM>順序一致性內存模型,易編程性:處理器內存模型
三 java內存模型
Java內存模型(Java Memory Model ,JMM)是一種符合內存模型規范的,屏蔽了各種硬件和操作系統的訪問差異的,保證了Java程序在各種平臺下對內存的訪問都能保證效果一致的機制及規范。
Java內存模型規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程的工作內存中保存了該線程中是用到的變量的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存。不同的線程之間也無法直接訪問對方工作內存中的變量,線程間變量的傳遞均需要自己的工作內存和主存之間進行數據同步進行。主內存和工作內存可類比成計算機內存模型中的主存和緩存的概念。
3.1 java內存模型解決并發問題方法
原子性,在java中,只有簡單的讀取、賦值(而且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作。在32位平臺下,對64位數據的賦值是需要通過兩個操作來完成,不能保證其原子性。要實現更大范圍操作的原子性,可以通過synchronized和Lock來實現。由于synchronized和Lock保證任一時刻只有一個線程執行該代碼塊,從而保證了原子性。
可見性,Java提供了volatile關鍵字來保證可見性,當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。
JMM通過happens-before關系向程序員提供跨線程的內存可見性保證:
有序性,可以使用synchronized和volatile來保證多線程之間操作的有序性。實現方式有所區別:volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只允許一條線程操作。
3.2 java并發原語
Java內存模型,除了定義了一套規范,還提供了一系列原語,封裝了底層實現后,供開發者直接使用。
3.2.1 volatile
內存語義:
當寫一個volatile變量時,JMM會把該線程對應的本地內存中的所有共享變量刷新到主內存。
當讀一個volatile變量,JMM會把該線程對應的本地內存置為無效,線程接下來從主內存中讀取共享變量。
實現:
編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。
在每個volatile寫操作前面插入一個StoreStore屏障。StoreStore屏障禁止上面的普通寫和volatile寫重排序,保障上面的普通寫在volatile寫之前刷新到主內存。
在每個volatile寫操作后面插入一個StoreLoad屏障。避免volatile寫與后面可能有的volatile讀/寫重排序。
在每個volatile讀操作的后面插入一個LoadLoad屏障。禁止下面的普通讀操作和上面的volatile讀操作重排序
在每個volatile讀操作的后面插入一個LoadStore屏障。禁止下面的普通寫操作和上面的volatile讀操作重排序
3.2.2 synchronized
內存語義:
當線程釋放鎖時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存中.
當線程獲取鎖時,JMM會把該線程對應的本地內存置為無效。從而使得被監視器保護的臨界區代碼必須從主內存中讀取共享變量.
實現:
java對象頭組成:
Mark Word用于加鎖操作,結構如下:
圖3.1 java對象頭Mark Word
synchronized用的鎖是存在Java對象頭里,任何java對象都存在一個鎖,JVM基于進入和退出Monitor對象來實現方法同步和代碼塊同步。代碼塊同步是使用monitorenter和monitorexit指令實現的,monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處。
監視器鎖(Monitor)本質依賴操作系統的Mutex Lock(互斥鎖)來實現,如果互斥量已經上鎖,調用線程會阻塞,阻塞或喚醒一條線程,都需要操作系統來幫忙完成,這就需要從用戶態轉換到核心態中,因此狀態轉換需要耗費很多的處理器時間。在jdk1.6中加入對鎖的優化措施,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。鎖可以升級但不能降級。
偏向鎖:
當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。引入偏向鎖是為了在無多線程競爭的情況下盡量減少不必要的輕量級鎖執行路徑,因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令(由于一旦出現多線程競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小于節省下來的CAS原子指令的性能消耗)。
輕量級鎖:
輕量級鎖是為了在線程近乎交替執行同步塊時提高性能。多個線程競爭鎖,若當前只有一個等待線程,則可通過自旋稍微等待一下,可能另一個線程很快就會釋放鎖。 但是當自旋超過一定的次數,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖膨脹為重量級鎖。
重量級鎖:
重量級鎖是通過對象內部的一個叫做監視器鎖(monitor)來實現的,監視器鎖本質又是依賴于底層的操作系統的Mutex Lock(互斥鎖)來實現的。而操作系統實現線程之間的切換需要從用戶態轉換到核心態,這個成本非常高。
其它鎖優化措施:鎖消除、鎖粗化、自旋鎖(忙循環,適用持有鎖的線程很快釋放鎖)、自適應的自旋鎖(自旋次數不固定,前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態決定)。
3.2.3 final
寫final域禁止把final域的寫重排序到構造函數之外。對于引用類型:在構造函數內對final域引用對象的成員域的寫入,與在構造函數外將這個被構造對象的引用賦值給引用變量,這兩個操作不能重排序。防止對象構造完成,未被初始化的final域被訪問(要達到此目的,還需確保被構造對象不能在構造函數中“逸出”)
讀final域禁止初次讀一個對象的引用和隨后初次讀這個對象包含的final域之間的重排序。確保在讀一個對象的final域前,一定會先讀包含這個final域對象的引用,如果引用不為空,引用對象的final域已經被初始化過。
實現:
JMM禁止編譯器把final域的寫重排序到構造函數之外。
編譯器在final域的寫之后,構造函數return之前,插入StoreStore屏障,禁止處理器把final域的寫重排序到構造函數之外。
編譯器會在讀final域前面插入StoreStore屏障。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的内存位置访问无效 midas.dll_java并发之内存模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: idea 2020.2 如何设置clas
- 下一篇: 计算机应用的时间地点意义,计算机应用在教