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