kxbin
          成功留給有準(zhǔn)備的人
          posts - 10,  comments - 35,  trackbacks - 0

          很多程序員對(duì)一個(gè)共享變量初始化要注意可見性和安全發(fā)布(安全地構(gòu)建一個(gè)對(duì)象,并其他線程能正確訪問)等問題不是很理解,認(rèn)為Java是一個(gè)屏蔽內(nèi)存細(xì)節(jié)的平臺(tái),連對(duì)象回收都不需要關(guān)心,因此談到可見性和安全發(fā)布大多不知所云。其實(shí)關(guān)鍵在于對(duì)Java存儲(chǔ)模型,可見性和安全發(fā)布的問題是起源于Java的存儲(chǔ)結(jié)構(gòu)。

          Java存儲(chǔ)模型原理

          有很多書和文章都講解過Java存儲(chǔ)模型,其中一個(gè)圖很清晰地說明了其存儲(chǔ)結(jié)構(gòu):

          由上圖可知, jvm系統(tǒng)中存在一個(gè)主內(nèi)存(Main Memory或Java Heap Memory),Java中所有變量都儲(chǔ)存在主存中,對(duì)于所有線程都是共享的。 每條線程都有自己的工作內(nèi)存(Working Memory),工作內(nèi)存中保存的是主存中某些變量的拷貝,線程對(duì)所有變量的操作都是在工作內(nèi)存中進(jìn)行,線程之間無法相互直接訪問,變量傳遞均需要通過主存完成。

          這個(gè)存儲(chǔ)模型很像我們常用的緩存與數(shù)據(jù)庫的關(guān)系,因此由此可以推斷JVM如此設(shè)計(jì)應(yīng)該是為了提升性能,提高多線程的并發(fā)能力,并減少線程之間的影響。

          Java存儲(chǔ)模型潛在的問題

          一談到緩存 我們立馬想到會(huì)有緩存不一致性問題,就是說當(dāng)有緩存與數(shù)據(jù)庫不一致的時(shí)候,就需要有相應(yīng)的機(jī)制去同步數(shù)據(jù)。同理,Java存儲(chǔ)模型也有這個(gè)問題,當(dāng)一個(gè)線程在自己工作內(nèi)存里初始化一個(gè)變量,當(dāng)還沒來得及同步到主存里時(shí),如果有其他線程來訪問它,就會(huì)出現(xiàn)不可預(yù)知的問題。另外,JVM在底層設(shè)計(jì)上,對(duì)與那些沒有同步到主存里的變量,可能會(huì)以不一樣的操作順序來執(zhí)行指令,舉個(gè)實(shí)際的例子:

           

          1. public class PossibleReordering {  
          2.     static int x = 0, y = 0;  
          3.     static int a = 0, b = 0;  
          4.     public static void main(String[] args)  
          5.             throws InterruptedException {  
          6.         Thread one = new Thread(new Runnable() {  
          7.             public void run() {  
          8.                 a = 1;  
          9.                 x = b;  
          10.             }  
          11.         });  
          12.         Thread other = new Thread(new Runnable() {  
          13.             public void run() {  
          14.                 b = 1;  
          15.                 y = a;  
          16.             }  
          17.         });  
          18.         one.start(); other.start();  
          19.         one.join();   other.join();  
          20.         System.out.println("( "+ x + "," + y + ")");  
          21.     }  
          22. }  

           

          由于,變量x,y,a,b沒有安全發(fā)布,導(dǎo)致會(huì)不以規(guī)定的操作順序來執(zhí)行這次四次賦值操作,有可能出現(xiàn)以下順序:

          出現(xiàn)這個(gè)問題也可以理解,因?yàn)榧热贿@些對(duì)象不可見,也就是說本應(yīng)該隔離在各個(gè)線程的工作區(qū)內(nèi),那么對(duì)于有些無關(guān)順序的指令,打亂順序執(zhí)行在JVM看來也是可行的。

          因此,總結(jié)起來,會(huì)有以下兩種潛在問題:

          • 緩存不一致性
          • 重排序執(zhí)行

          解決Java存儲(chǔ)模型潛在的問題

          為了能讓開發(fā)人員安全正確地在Java存儲(chǔ)模型上編程,JVM提供了一個(gè)happens-before原則,有人整理得非常好,我摘抄如下:

          • 在程序順序中, 線程中的每一個(gè)操作, 發(fā)生在當(dāng)前操作后面將要出現(xiàn)的每一個(gè)操作之前.
          • 對(duì)象監(jiān)視器的解鎖發(fā)生在等待獲取對(duì)象鎖的線程之前.
          • 對(duì)volitile關(guān)鍵字修飾的變量寫入操作, 發(fā)生在對(duì)該變量的讀取之前.
          • 對(duì)一個(gè)線程的 Thread.start() 調(diào)用 發(fā)生在啟動(dòng)的線程中的所有操作之前.
          • 線程中的所有操作 發(fā)生在從這個(gè)線程的 Thread.join()成功返回的所有其他線程之前.

          有了原則還不夠,Java提供了以下工具和方法來保證變量的可見性和安全發(fā)布:

          • 使用 synchronized來同步變量初始化。此方式會(huì)立馬把工作內(nèi)存中的變量同步到主內(nèi)存中
          • 使用 volatile關(guān)鍵字來標(biāo)示變量。此方式會(huì)直接把變量存在主存中而不是工作內(nèi)存中
          • final變量。常量?jī)?nèi)也是存于主存中

          另外,一定要明確只有共享變量才會(huì)有以上那些問題,如果變量只是這個(gè)線程自己使用,就不用擔(dān)心那么多問題了

          搞清楚Java存儲(chǔ)模型后,再來看共享對(duì)象可見性和安全發(fā)布的問題就較為容易了

          共享對(duì)象的可見性

          當(dāng)對(duì)象在從工作內(nèi)存同步到主內(nèi)存之前,那么它就是不可見的。若有其他線程在存取不可見對(duì)象就會(huì)引發(fā)可見性問題,看下面一個(gè)例子:

           

          1. public class NoVisibility {  
          2.     private static boolean ready;  
          3.     private static int number;  
          4.     private static class ReaderThread extends Thread {  
          5.         public void run() {  
          6.             while (!ready)  
          7.                 Thread.yield();  
          8.             System.out.println(number);  
          9.         }  
          10.     }  
          11.     public static void main(String[] args) {  
          12.         new ReaderThread().start();  
          13.         number = 42;  
          14.         ready = true;  
          15.     }  
          16. }  

           

          按照正常邏輯,應(yīng)該會(huì)輸出42,但其實(shí)際結(jié)果會(huì)非常奇怪,可能會(huì)永遠(yuǎn)沒有輸出(因?yàn)閞eady為false),可能會(huì)輸出0(因?yàn)橹嘏判騿栴}導(dǎo)致ready=true先執(zhí)行)。再舉一個(gè)更為常見的例子,大家都喜歡用只有set和get方法的pojo來設(shè)計(jì)領(lǐng)域模型,如下所示:

           

          1. @NotThreadSafe  
          2. public class MutableInteger {  
          3.     private int value;  
          4.     public int  get() { return value; }  
          5.     public void set(int value) { this.value = value; }  
          6. }  

           

          但是,當(dāng)有多個(gè)線程同時(shí)來存取某一個(gè)對(duì)象時(shí),可能就會(huì)有類似的可見性問題。

          為了保證變量的可見性,一般可以用鎖、 synchronized關(guān)鍵字、 volatile關(guān)鍵字或直接設(shè)置為final

          共享變量發(fā)布

          共享變量發(fā)布和我們常說的發(fā)布程序類似,就是說讓本屬于內(nèi)部的一個(gè)變量變?yōu)橐粋€(gè)可以被外部訪問的變量。發(fā)布方式分為以下幾種:

          • 將對(duì)象引用存儲(chǔ)到公共靜態(tài)域
          • 初始化一個(gè)可以被外部訪問的對(duì)象
          • 將對(duì)象引用存儲(chǔ)到一個(gè)集合里

          安全發(fā)布和保證可見性的方法類似,就是要同步發(fā)布動(dòng)作,并使發(fā)布后的對(duì)象可見。

          線程安全

          其實(shí)當(dāng)我們把這些變量封閉在本線程內(nèi)訪問,就可以從根本上避免以上問題,現(xiàn)實(shí)中存在很多例子通過線程封閉來安全使用本不是線程安全的對(duì)象,比如:

          • swing的可視化組件和數(shù)據(jù)模型對(duì)象并不是線程安全的,它通過將它們限制到swing的事件分發(fā)線程中,實(shí)現(xiàn)線程安全
          • JDBC Connection對(duì)象沒有要求為線程安全,但JDBC的存取模式?jīng)Q定了一個(gè)Connection只會(huì)同時(shí)被一個(gè)線程使用
          • ThreadLocal把變量限制在本線程中共享
          posted on 2011-10-13 15:47 kxbin 閱讀(352) 評(píng)論(0)  編輯  收藏 所屬分類: java基礎(chǔ)
          你恨一個(gè)人是因?yàn)槟銗鬯荒阆矚g一個(gè)人,是因?yàn)樗砩嫌心銢]有的;你討厭一個(gè)人是因?yàn)樗砩嫌心阌械臇|西;你經(jīng)常在別人面前批評(píng)某人,其實(shí)潛意識(shí)中是想接近他。

          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(5)

          隨筆檔案

          文章分類

          文章檔案

          相冊(cè)

          收藏夾

          J2EE

          java技術(shù)網(wǎng)站

          Linux

          平時(shí)常去的網(wǎng)站

          數(shù)據(jù)庫

          電影網(wǎng)站

          網(wǎng)站設(shè)計(jì)

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 汉阴县| 通渭县| 南陵县| 沈丘县| 东丰县| 和硕县| 贡嘎县| 稷山县| 法库县| 中牟县| 马龙县| 佛学| 九台市| 江阴市| 永城市| 禄丰县| 德格县| 永登县| 梓潼县| 津市市| 天全县| 金沙县| 上犹县| 忻州市| 华阴市| 广水市| 平阳县| 新竹市| 罗定市| 凉城县| 滦平县| 项城市| 北碚区| 汾阳市| 仲巴县| 古交市| 象山县| 上杭县| 绥宁县| 独山县| 九寨沟县|