摘要:一主存儲器與工作存儲器內存模型分為主存儲器和工作存儲器兩種。工作存儲器每個線程各自獨立所擁有的作業區,在中,存有中的部分拷貝,稱之為工作拷貝。注意線程欲退出時,不會執行工作存儲器的釋放操作。
一、主存儲器與工作存儲器
Java內存模型(memory model)分為主存儲器(main memory)和工作存儲器(working memory)兩種。
主存儲器(main memory):
類的實例所存在的區域,main memory為所有的線程所共享。
工作存儲器(working memory):
每個線程各自獨立所擁有的作業區,在working memory中,存有main memory中的部分拷貝,稱之為工作拷貝(working copy)。
線程無法直接對主存儲器進行操作,當線程需要引用實例的字段的值時,會一次將字段值從主存儲器拷貝到工作存儲器上(相當于上圖中的read->load)。
當線程再次需要引用相同的字段時,可能直接使用剛才的工作拷貝(use),也可能重新從主存儲器獲取(read->load->use)。
具體會出現哪種情況,由JVM決定。
由于線程無法直接對主存儲器進行操作,所以也就無法直接將值指定給字段。
當線程欲將值指定給字段時,會一次將值指定給位于工作存儲器上的工作拷貝(assign),指定完成后,工作拷貝的內容便會復制到主存儲器(store->write),至于何時進行復制,由JVM決定。
因此,當線程反復對一個實例的字段進行賦值時,可能只會對工作拷貝進行指定(assign),此時只有指定的最后結果會在某個時刻拷貝到主存儲器(store-write);也可能在每次指定時,都進行拷貝到主存儲器的操作(assign->store->write)。
Java語言規范定義了線程的六種原子操作:
read
負責從主存儲器(main memory)拷貝到工作存儲器(working memory)
write
與上述相反,負責從工作存儲器(working memory)拷貝到主存儲器(main memory)
use
表示線程引用工作存儲器(working memory)的值
assign
表示線程將值指定給工作存儲器(working memory)
lock
表示線程取得鎖定
unlock
表示線程解除鎖定
線程欲進入synchronized時,會執行以下兩類操作:
強制寫入主存儲器(main memory)
當線程欲進入synchronized時,如果該線程的工作存儲器(working memory)上有未映像到主存儲器的拷貝,則這些內容會強制寫入主存儲器(store->write),則這些計算結果就會對其它線程可見(visible)。
工作存儲器(working memory)的釋放
當線程欲進入synchronized時,工作存儲器上的工作拷貝會被全部丟棄。之后,欲引用主存儲器上的值的線程,必定會從主存儲器將值拷貝到工作拷貝(read->load)。
4.2 線程欲退出synchronized線程欲退出synchronized時,會執行以下操作:
強制寫入主存儲器(main memory)
當線程欲退出synchronized時,如果該線程的工作存儲器(working memory)上有未映像到主存儲器的拷貝,則這些內容會強制寫入主存儲器(store->write),則這些計算結果就會對其它線程可見(visible)。
注意: 線程欲退出synchronized時,不會執行工作存儲器(working memory)的釋放 操作。
五、volatile的本質volatile具有以下兩種功能:
進行內存同步
volatile只能做內存同步,不能取代synchronized關鍵字做線程同步。
當線程欲引用volatile字段的值時,通常都會發生從主存儲器到工作存儲器的拷貝操作;相反的,將值指定給寫著volatile的字段后,工作存儲器的內容通常會立即映像到主存儲器
以原子(atomic)方式進行long、double的指定
六、Double Checked Locking Pattern的危險性 6.1 可能存在缺陷的單例模式設計模式中有一種單例模式(Singleton Pattern),通常采用鎖來保證線程的安全性。
Main類:
//兩個Main線程同時調用單例方法getInstance public class Main extends Thread { public static void main(String[] args) { new Main().start(); new Main().start(); } public void run() { System.out.println(Thread.currentThread().getName() + ":" + MySystem.getInstance().getDate()); } }
單例類:
//采用延遲加載+雙重鎖的形式保證線程安全以及性能 public class MySystem { private static MySystem instance = null; private Date date = new Date(); ? private MySystem() { } public Date getDate() { return date; } public static MySystem getInstance() { if (instance == null) { synchronized (MySystem.class) { if (instance == null) { instance = new MySystem(); } } } return instance; } }
分析:
上述Main類的MySystem.getInstance().getDate()調用可能返回null或其它值。
假設有兩個線程A和B,按照以下順序執行:
當線程A執行完A-4且未退出synchronized時,線程B開始執行,此時B獲得了A創建好的instance實例。
但是注意,此時instance實例可能并未完全初始化完成。
這是因為線程A制作MySystem實例時,會給date字段指定值new Date(),此時可能只完成了assign操作(線程A對工作存取器上的工作拷貝進行指定),在線程A退出synchronized時,線程A的工作存儲器上的值不保證一定會映像到主存儲器上(store->write)。
所以,當線程B在線程A退出前就調用MySystem.getInstance().getDate()方法的話,由于主存儲器上的date字段并未被賦值過,所以B得到的date字段就是未初始化過的。
注意:上面描述的這種情況是否真的會發生,取決于JVM,由Java語言規范決定。
解決方法:
采用懶加載模式,在MySystem類中直接為instance 字段賦值:
private static MySystem instance = new MySystem();
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71490.html
摘要:線程之間的通信由內存模型本文簡稱為控制,決定一個線程對共享變量的寫入何時對另一個線程可見。為了保證內存可見性,編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序。 并發編程模型的分類 在并發編程中,我們需要處理兩個關鍵問題:線程之間如何通信及線程之間如何同步(這里的線程是指并發執行的活動實體)。通信是指線程之間以何種機制來交換信息。在命令式編程中,線程之間的...
摘要:前情提要深入理解內存模型一基礎編譯器運行時會對指令進行重排序。以處理器的猜測執行為例,執行線程的處理器可以提前讀取并計算,然后把計算結果臨時保存到一個名為重排序緩沖的硬件緩存中。請看下篇深入理解內存模型三順序一致性 前情提要 深入理解Java內存模型(一)——基礎 Java編譯器、運行時會對指令進行重排序。這種重排序在單線程和多線程情況下分別有什么影響呢? 數據依賴性 如果兩個操...
摘要:如問到是否使用某框架,實際是是問該框架的使用場景,有什么特點,和同類可框架對比一系列的問題。這兩個方向的區分點在于工作方向的側重點不同。 [TOC] 這是一份來自嗶哩嗶哩的Java面試Java面試 32個核心必考點完全解析(完) 課程預習 1.1 課程內容分為三個模塊 基礎模塊: 技術崗位與面試 計算機基礎 JVM原理 多線程 設計模式 數據結構與算法 應用模塊: 常用工具集 ...
摘要:目的是解決由于多線程通過共享內存進行通信時,存在的原子性可見性緩存一致性以及有序性問題。最多只有一個線程能持有鎖。線程加入規則對象的結束先行發生于方法返回。 前言 學習情況記錄 時間:week 1 SMART子目標 :Java 多線程 學習Java多線程,要了解多線程可能出現的并發現象,了解Java內存模型的知識是必不可少的。 對學習到的重要知識點進行的記錄。 注:這里提到的是Ja...
閱讀 2212·2021-11-22 13:52
閱讀 3847·2021-11-10 11:36
閱讀 1380·2021-09-24 09:47
閱讀 1088·2019-08-29 13:54
閱讀 3360·2019-08-29 13:46
閱讀 1942·2019-08-29 12:16
閱讀 2108·2019-08-26 13:26
閱讀 3471·2019-08-23 17:10