<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    kxbin
    成功留給有準備的人
    posts - 10,  comments - 35,  trackbacks - 0

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

    Java存儲模型原理

    有很多書和文章都講解過Java存儲模型,其中一個圖很清晰地說明了其存儲結構:

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

    這個存儲模型很像我們常用的緩存與數據庫的關系,因此由此可以推斷JVM如此設計應該是為了提升性能,提高多線程的并發能力,并減少線程之間的影響。

    Java存儲模型潛在的問題

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

     

    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沒有安全發布,導致會不以規定的操作順序來執行這次四次賦值操作,有可能出現以下順序:

    出現這個問題也可以理解,因為既然這些對象不可見,也就是說本應該隔離在各個線程的工作區內,那么對于有些無關順序的指令,打亂順序執行在JVM看來也是可行的。

    因此,總結起來,會有以下兩種潛在問題:

    • 緩存不一致性
    • 重排序執行

    解決Java存儲模型潛在的問題

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

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

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

    • 使用 synchronized來同步變量初始化。此方式會立馬把工作內存中的變量同步到主內存中
    • 使用 volatile關鍵字來標示變量。此方式會直接把變量存在主存中而不是工作內存中
    • final變量。常量內也是存于主存中

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

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

    共享對象的可見性

    當對象在從工作內存同步到主內存之前,那么它就是不可見的。若有其他線程在存取不可見對象就會引發可見性問題,看下面一個例子:

     

    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. }  

     

    按照正常邏輯,應該會輸出42,但其實際結果會非常奇怪,可能會永遠沒有輸出(因為ready為false),可能會輸出0(因為重排序問題導致ready=true先執行)。再舉一個更為常見的例子,大家都喜歡用只有set和get方法的pojo來設計領域模型,如下所示:

     

    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. }  

     

    但是,當有多個線程同時來存取某一個對象時,可能就會有類似的可見性問題。

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

    共享變量發布

    共享變量發布和我們常說的發布程序類似,就是說讓本屬于內部的一個變量變為一個可以被外部訪問的變量。發布方式分為以下幾種:

    • 將對象引用存儲到公共靜態域
    • 初始化一個可以被外部訪問的對象
    • 將對象引用存儲到一個集合里

    安全發布和保證可見性的方法類似,就是要同步發布動作,并使發布后的對象可見。

    線程安全

    其實當我們把這些變量封閉在本線程內訪問,就可以從根本上避免以上問題,現實中存在很多例子通過線程封閉來安全使用本不是線程安全的對象,比如:

    • swing的可視化組件和數據模型對象并不是線程安全的,它通過將它們限制到swing的事件分發線程中,實現線程安全
    • JDBC Connection對象沒有要求為線程安全,但JDBC的存取模式決定了一個Connection只會同時被一個線程使用
    • ThreadLocal把變量限制在本線程中共享
    posted on 2011-10-13 15:47 kxbin 閱讀(353) 評論(0)  編輯  收藏 所屬分類: java基礎
    你恨一個人是因為你愛他;你喜歡一個人,是因為他身上有你沒有的;你討厭一個人是因為他身上有你有的東西;你經常在別人面前批評某人,其實潛意識中是想接近他。

    <2025年7月>
    293012345
    6789101112
    13141516171819
    20212223242526
    272829303112
    3456789

    常用鏈接

    留言簿(5)

    隨筆檔案

    文章分類

    文章檔案

    相冊

    收藏夾

    J2EE

    java技術網站

    Linux

    平時常去的網站

    數據庫

    電影網站

    網站設計

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 亚洲国产成人AV网站| 性感美女视频免费网站午夜| 亚洲日韩精品国产3区| 亚洲VA中文字幕无码毛片| 免费看一级做a爰片久久| 免费观看国产网址你懂的| 中文字幕无码一区二区免费| 日韩精品视频在线观看免费| 亚洲熟女精品中文字幕| 精品亚洲A∨无码一区二区三区| 亚洲精品国产自在久久| 韩国欧洲一级毛片免费| 在线观看av永久免费| 亚洲高清视频免费| 久久国产乱子伦精品免费看| 国产乱子伦精品免费视频| 国产亚洲视频在线观看网址| 亚洲午夜精品一区二区麻豆| 亚洲欧洲日产国码在线观看| 亚洲成人动漫在线| 九月丁香婷婷亚洲综合色| 久久久久国产成人精品亚洲午夜| 国产zzjjzzjj视频全免费| 日韩免费毛片视频| 日本久久久免费高清| 欧洲精品免费一区二区三区| 午夜高清免费在线观看| 成人性生交大片免费看无遮挡| 青青青免费国产在线视频小草| 久久精品国产免费观看三人同眠| 四虎国产精品永久免费网址| 免费av一区二区三区| 久久久久国产精品免费网站| 久久久久久久久久国产精品免费| 人妻在线日韩免费视频| 免费观看久久精彩视频| 人妻丰满熟妇无码区免费| 亚洲免费在线观看视频| 波多野结衣在线免费观看| 免费无码A片一区二三区| 日韩高清在线免费观看|