java的可見(jiàn)性献丑、有序性和原子性

話不多說(shuō),先上一張圖

java內(nèi)存模(jie)型(gou)

沒(méi)錯(cuò)侠姑,我們今天聊的東西创橄,跟他沒(méi)啥關(guān)系。

上面這是java的內(nèi)存結(jié)構(gòu)(我就是忽悠你們來(lái)的)莽红。

今兒主要先聊一聊java的內(nèi)存模型(嗯妥畏,也不是非想跟你們聊,主要是標(biāo)題得從這玩意兒來(lái)引出)安吁。

但是也不能干聊不是醉蚁?想起兄弟們?cè)?jīng)對(duì)我靈魂的拷問(wèn)(無(wú)圖言屌),所以我就又從網(wǎng)上盜了一張圖鬼店。

來(lái)來(lái)來(lái)网棍,看圖說(shuō)話:

java真·內(nèi)存模型

1.java中所有的變量都是存在主內(nèi)存里的。

2.各自的線程在工作的時(shí)候會(huì)自己拿到一塊工作內(nèi)存妇智。里面保存了該線程用到的變量的副本滥玷。

3.線程對(duì)變量的操作氏身,都是對(duì)自己工作內(nèi)存中變量的操作,不能操作主內(nèi)存罗捎。

4.最終工作內(nèi)存中的數(shù)據(jù)是需要同步回主內(nèi)存观谦,來(lái)完成主內(nèi)存中變量的更新的。

舉個(gè)例子吧:

就好比主內(nèi)存就是大哥桨菜,線程就是小弟豁状,大哥每天督促小弟們干活,小弟們彼此不怎么交流倒得,每天就是干了活泻红,然后自己找個(gè)本記下來(lái),找時(shí)間告訴大哥霞掺。

那么咱們來(lái)看下面的這一個(gè)場(chǎng)景:

大哥說(shuō)讓仨小弟進(jìn)點(diǎn)兒貨谊路。讓一個(gè)小弟聯(lián)系貨源,一個(gè)小弟聯(lián)系運(yùn)輸公司菩彬,一個(gè)小弟聯(lián)系裝卸工缠劝。小弟1去聯(lián)系貨源了,聯(lián)系好了告訴大哥骗灶,“貨源聯(lián)系好了”惨恭。這時(shí)候小弟2來(lái)了,一聽(tīng)貨源好了耙旦,就去聯(lián)系運(yùn)輸公司了脱羡。運(yùn)輸公司聯(lián)系好了,小弟2還沒(méi)回去告訴大哥呢免都,小弟3跑過(guò)去問(wèn)大哥了锉罐,一聽(tīng)大哥說(shuō)貨源好了,也跑出去聯(lián)系運(yùn)輸公司了绕娘。結(jié)果來(lái)了倆運(yùn)輸公司脓规,對(duì)著成堆的貨物干瞪眼。

這里面有幾個(gè)問(wèn)題呢业舍?

1.小弟2忙活清了抖拦,應(yīng)該打個(gè)電話趕緊通知大哥,他找到運(yùn)輸公司了舷暮。

2.大哥知道小弟2出去忙活事情了态罪,小弟3這時(shí)候來(lái)了,大哥應(yīng)該別讓小弟3著急下面,等小弟2干完了复颈,小弟3再上。

3.小弟2要是告訴大哥,他出去找運(yùn)輸公司了耗啦,大哥怎么還會(huì)讓小弟3接著去找運(yùn)輸公司呢凿菩?

如果在這個(gè)場(chǎng)景里,我們會(huì)說(shuō)帜讲,這不一群智障么衅谷,這種錯(cuò)誤都能發(fā)生,辦事不過(guò)腦子的么似将?获黔!

是的,java就是這么的智障在验。

其實(shí)呢玷氏,上面闡述的三個(gè)問(wèn)題,也就是我們java內(nèi)存模型在設(shè)計(jì)的時(shí)候腋舌,所圍繞的三個(gè)問(wèn)題:

可見(jiàn)性盏触,有序性和原子性。(終于聊到重點(diǎn)了朋友們?榻取)

下面要上定義了赞辩!

可見(jiàn)性:一個(gè)線程對(duì)共享變量做了修改之后,其他的線程立即能夠看到(感知到)該變量這種修改(變化)授艰。

Java內(nèi)存模型是通過(guò)將在工作內(nèi)存中的變量修改后的值同步到主內(nèi)存诗宣,在讀取變量前從主內(nèi)存刷新最新值到工作內(nèi)存中,這種依賴(lài)主內(nèi)存的方式來(lái)實(shí)現(xiàn)可見(jiàn)性的想诅。

有序性:在本線程內(nèi)觀察,操作都是有序的岛心;如果在一個(gè)線程中觀察另外一個(gè)線程来破,所有的操作都是無(wú)序的。

java內(nèi)存模型所保證的是忘古,同線程內(nèi)徘禁,所有的操作都是由上到下的,但是多個(gè)線程并行的情況下髓堪,則不能保證其操作的有序性送朱。

原子性:一個(gè)操作不能被打斷,要么全部執(zhí)行完畢干旁,要么不執(zhí)行驶沼。

基本數(shù)據(jù)類(lèi)型的訪問(wèn)都是原子性的(默認(rèn)64位,32位的機(jī)器對(duì)long争群、double這種占8個(gè)字節(jié)的是非原子性的)回怜,而針對(duì)非原子性的數(shù)據(jù),多線程的訪問(wèn)則是不安全的换薄。

以上是java內(nèi)存模型中玉雾,單線程針對(duì)這三種問(wèn)題作出的最基本的控制翔试,但是并發(fā)編程的場(chǎng)景中, 多線程的出現(xiàn)會(huì)導(dǎo)致這三個(gè)問(wèn)題頻頻發(fā)生复旬。

那么聰明的你一定會(huì)問(wèn)了垦缅,我們應(yīng)該如何去控制呢?(不問(wèn)的都拉出去彈雞兒)

一個(gè)一個(gè)來(lái):

可見(jiàn)性:

volatile關(guān)鍵字:通過(guò)volatile關(guān)鍵字修飾內(nèi)存中的變量驹碍,該變量在線程之間共享

其實(shí)對(duì)于可見(jiàn)性而言壁涎,無(wú)論是普通變量還是volatile變量都是如此,區(qū)別在于:volatile的特殊規(guī)則保證了volatile變量值修改后的新值立刻同步到主內(nèi)存幸冻,每次使用volatile變量前立即從主內(nèi)存中刷新粹庞,因此volatile保證了多線程之間的操作變量的可見(jiàn)性,而普通變量則不能保證這一點(diǎn)洽损。

但是volatile只是對(duì)關(guān)鍵字進(jìn)行了修飾庞溜,保證了其可見(jiàn)性,而對(duì)于線程的有序性和變量的原子性碑定,volatile根本沒(méi)有什么卵用流码,也就是什么意思呢?

   private static volatile int volatileCount = 0;
   private static void volatileCount() {
        for (int i = 0; i < 10; i++) {
            Executors.newFixedThreadPool(3).execute(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("volatile count: " + ++volatileCount);
            });
        }
    }

代碼中的volatile延刘,加與不加漫试,沒(méi)多少區(qū)別。
因?yàn)関olatile只能保證其變量的可見(jiàn)性碘赖。但是每個(gè)線程啥時(shí)候開(kāi)始跑驾荣,我們根本不知道。當(dāng)A線程訪問(wèn)到volatileCount = 1時(shí)普泡,并且開(kāi)始執(zhí)行時(shí)播掷,如果B線程此時(shí)也訪問(wèn)到了,他也一樣會(huì)執(zhí)行撼班,雖然讀取volatileCount時(shí)歧匈,我們都能保證他是最新的,但是我們不能保證砰嘁,當(dāng)A線程在操作的時(shí)候件炉,B線程不能進(jìn)去插一腳。

原子性:
java.util.concurrent.atomic矮湘,原子類(lèi)了解一下斟冕?
在java中,我們知道++操作實(shí)際上并不是線程安全的缅阳,為了保證線程間的變量原子性宫静,java引入了atomic類(lèi)。它的作用就是保證,使用的變量孤里,一定是原子性的伏伯。
代碼咱看一下:

    private static AtomicInteger atomicCount = new AtomicInteger(0);
    private static void atomicCount() {
        for (int i = 0; i < 10; i++) {
            Executors.newFixedThreadPool(3).execute(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //atomicCount.incrementAndGet()方法的意思是讓其自增 1,等同于++
                System.out.println("atomic count: " + atomicCount.incrementAndGet());
            });
        }
    }
    //    atomic count: 5
    //    atomic count: 1
    //    atomic count: 3
    //    atomic count: 2
    //    atomic count: 8
    //    atomic count: 10
    //    atomic count: 4
    //    atomic count: 6
    //    atomic count: 7
    //    atomic count: 9

結(jié)果也放在下面了捌袜,可見(jiàn)说搅,我們保證了它的唯一性,他是一定會(huì)自增到10的虏等。說(shuō)白了弄唧,就是緩存鎖機(jī)制。
關(guān)于緩存鎖的機(jī)制霍衫,下面有一個(gè)官方解釋?zhuān)梢詫W(xué)習(xí)一下:

緩存鎖是指通過(guò)鎖住CPU緩存候引,在CPU緩存區(qū)實(shí)現(xiàn)共享變量的原子性操作。
如果緩存在處理器的緩存行中敦跌,內(nèi)存區(qū)域在LOCK操作期間被鎖定澄干,當(dāng)它執(zhí)行鎖操作,回寫(xiě)主內(nèi)存時(shí)柠傍,處理器不在總線鎖上聲言LOCK#信號(hào)麸俘,而是修改內(nèi)部?jī)?nèi)存地址,并允許它的緩存一致性機(jī)制來(lái)保證操作的原子性惧笛。因?yàn)榫彺嬉恢滦詸C(jī)制會(huì)阻止同時(shí)修改被兩個(gè)以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù)从媚,當(dāng)其他處理器回寫(xiě)已被鎖定的緩存行的數(shù)據(jù)時(shí)會(huì)起緩存行無(wú)效。
緩存鎖使用的是比較并交換策略(Compare And Swap簡(jiǎn)稱(chēng)CAS)患整,CAS操作需要輸入兩個(gè)數(shù)值拜效,一個(gè)舊值(期望操作前的值)和一個(gè)新值,在操作期間先比較下舊值有沒(méi)有發(fā)生變化各谚,如果沒(méi)有發(fā)生變化拂檩,才交換成新值,發(fā)生了變化則不交換嘲碧。

這段話也解釋了為什么雖然我們不能保證其有序性,但是父阻,我們依然能正常的自增到10愈涩。

有序性:
synchronized,日常用的最多的東西加矛,當(dāng)使用synchronized關(guān)鍵字時(shí)履婉,只能有一個(gè)線程執(zhí)行直到執(zhí)行完成后或異常,才會(huì)釋放鎖斟览。所以可以保證synchronized代碼塊或方法只會(huì)有一個(gè)線程執(zhí)行毁腿,保障了程序的有序性。

    private static void synchronizedCount() {
        for (int i = 0; i < 10; i++) {
            Executors.newFixedThreadPool(3).execute(->() {
                @Override
                public void run() {
                    synchronized (MyClass.class) {  // 通過(guò)synchronized關(guān)鍵字來(lái)保證線程之間的有序性
                        System.out.println("synchronized count: " + ++synchronizedCount);
                    }
                }
            });
        }
    }

結(jié)果看上去很美好,有序已烤,且從1到10鸠窗,一個(gè)不差。
但是胯究,synchronized能不能保證原子性和可見(jiàn)性呢稍计?還記得單例的DCL寫(xiě)法么?

public class Singleton{
    
    private static volatile Singleton instance;
    
    private Singleton(){
    }
    
    public static  Singleton getInstance(){
        
        if(instance==null){
            synchronized(Singleton.class){
                if(instance==null){
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }
}

如果不加volatile會(huì)有什么問(wèn)題呢裕循?
java內(nèi)存模型(jmm)并不限制處理器重排序臣嚣,在執(zhí)行instance=new Singleton();時(shí)剥哑,并不是原子語(yǔ)句硅则,實(shí)際是包括了下面三大步驟:
1.為對(duì)象分配內(nèi)存
2.初始化實(shí)例對(duì)象
3.把引用instance指向分配的內(nèi)存空間
這個(gè)三個(gè)步驟并不能保證按序執(zhí)行,處理器會(huì)進(jìn)行指令重排序優(yōu)化株婴,存在這樣的情況:
優(yōu)化重排后執(zhí)行順序?yàn)椋?,3,2, 這樣在線程1執(zhí)行到3時(shí)怎虫,instance已經(jīng)不為null了,線程2此時(shí)判斷instance!=null督暂,則直接返回instance引用揪垄,但現(xiàn)在實(shí)例對(duì)象還沒(méi)有初始化完畢,此時(shí)線程2使用instance可能會(huì)造成程序崩潰逻翁。
所以說(shuō)饥努,實(shí)際上synchronized只是保證了其有序性,并沒(méi)有辦法保證其原子性八回,而其可見(jiàn)性酷愧,是依靠java的內(nèi)存模型來(lái)保證的。

OK缠诅,基本說(shuō)清溶浴,就此打住,老少爺們們管引,咱下期再見(jiàn)士败。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市褥伴,隨后出現(xiàn)的幾起案子谅将,更是在濱河造成了極大的恐慌,老刑警劉巖重慢,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饥臂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡似踱,警方通過(guò)查閱死者的電腦和手機(jī)隅熙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)稽煤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人囚戚,你說(shuō)我怎么就攤上這事酵熙。” “怎么了弯淘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵绿店,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我庐橙,道長(zhǎng)假勿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任态鳖,我火速辦了婚禮转培,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浆竭。我一直安慰自己浸须,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布邦泄。 她就那樣靜靜地躺著删窒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪顺囊。 梳的紋絲不亂的頭發(fā)上肌索,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音特碳,去河邊找鬼诚亚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛午乓,可吹牛的內(nèi)容都是我干的站宗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼益愈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梢灭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蒸其,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤敏释,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后枣接,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缺谴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年但惶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耳鸯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膀曾,死狀恐怖县爬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情添谊,我是刑警寧澤财喳,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站斩狱,受9級(jí)特大地震影響耳高,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜所踊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一泌枪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秕岛,春花似錦碌燕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至遏考,卻和暖如春慈鸠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诈皿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工林束, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稽亏。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓壶冒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親截歉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胖腾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344