1. 程式人生 > >java執行緒安全問題之靜態成員變數、例項成員變數、區域性變數

java執行緒安全問題之靜態成員變數、例項成員變數、區域性變數

Java執行緒安全問題之靜態成員變數、例項成員變數、區域性變數


java多執行緒程式設計中,存在很多執行緒安全問題,至於什麼是執行緒安全呢,給出一個通俗易懂的概念還是蠻難的,如同《java併發程式設計實踐》中所說:

   寫道

給執行緒安全下定義比較困難。存在很多種定義,如:“一個類在可以被多個執行緒安全呼叫時就是執行緒安全的”。 

 此處不贅述了,首先給出靜態變數、例項變數、區域性變數在多執行緒環境下的執行緒安全問題結論,然後用示例驗證,請大家擦亮眼睛,有錯必究,否則誤人子弟!

靜態成員變數執行緒非安全(無論單例或者非單例皆不安全)。

靜態變數即類變數,位於方法區,為所有物件共享,共享一份記憶體,一旦靜態變數被修改,其他物件均對修改可見,故執行緒非安全。

例項成員變數單例模式(只有一個物件例項singleton存在)執行緒非安全,非單例執行緒安全。

例項變數為物件例項私有,在虛擬機器的堆heap中分配,若在系統中只存在一個此物件的例項,在多執行緒環境下,“猶如”靜態變數那樣,被某個執行緒修改後,其他執行緒對修改均可見,故執行緒非安全(如,springmvc controller是單例的,非執行緒安全的);如果每個執行緒執行都是在不同的物件中,那物件與物件之間的例項變數的修改將互不影響,故執行緒安全(如,struts2 action預設是非單例的,每次請求在heap中new新的action例項,故struts2 action可以用例項成員變數)。

區域性變數執行緒安全執行緒封閉性Thread Confinement

每個執行緒執行時將會把區域性變數放在各自棧幀的工作記憶體中,執行緒間不共享,故不存線上程安全問題。

〖by self:上述所有的變數均指,共享的(Shared)和可變的(Mutable,進行了'read''write'操作)狀態變數,只有對這樣的變數討論執行緒安全才有意義,所有實際上上述的“區域性變數”一定是執行緒安全的,因為其不是共享的(Not Shared),至於非狀態變數,毫無疑問,是執行緒安全的。〗

一、靜態成員變數 

執行緒安全問題模擬:

----------------------------------------------------------------------------------

  1. /**   
  2.   * 執行緒安全問題模擬執行   
  3.   *  ------------------------------   
  4.   *       執行緒1      |    執行緒2   
  5.   *  ------------------------------   
  6.   *   static_i = 4;  | 等待   
  7.   *   static_i = 10; | 等待   
  8.   *    等待          | static_i = 4;   
  9.   *   static_i * 2;  | 等待   
  10.   *  -----------------------------  
  11.  * */
  12. publicclass Test implements Runnable    
  13. {    
  14.     privatestaticint static_i;//靜態變數   
  15.     publicvoid run()    
  16.     {    
  17.         static_i = 4;    
  18.         System.out.println("[" + Thread.currentThread().getName()    
  19.                 + "]獲取static_i 的值:" + static_i);    
  20.         static_i = 10;    
  21.         System.out.println("[" + Thread.currentThread().getName()    
  22.                 + "]獲取static_i*2的值:" + static_i * 2);    
  23.     }    
  24.     publicstaticvoid main(String[] args)    
  25.     {    
  26.         Test t = new Test();    
  27.         //啟動儘量多的執行緒才能很容易的模擬問題   
  28.         for (int i = 0; i < 3000; i++)    
  29.         {    
  30.             //t可以換成new Test(),保證每個執行緒都在不同的物件中執行,結果一樣   
  31.             new Thread(t, "執行緒" + i).start();    
  32.         }    
  33.     }    
  34. }    

根據程式碼註釋中模擬的情況,當執行緒1執行了static_i = 4;  static_i = 10; 後,執行緒2獲得執行權,static_i = 4; 然後當執行緒1獲得執行權執行static_i * 2;  必然輸出結果4*2=8,按照這個模擬,我們可能會在控制檯看到輸出為8的結果。

寫道 [執行緒27]獲取static_i 的值:4 
[執行緒22]獲取static_i*2的值:20 
[執行緒28]獲取static_i 的值:4 
[執行緒23]獲取static_i*2的值:8 
[執行緒29]獲取static_i 的值:4 
[執行緒30]獲取static_i 的值:4 
[執行緒31]獲取static_i 的值:4 
[執行緒24]獲取static_i*2的值:20

 看紅色標註的部分,確實出現了我們的預想,同樣也證明了我們的結論。

二、例項成員變數

執行緒安全問題模擬:

----------------------------------------------------------------------------------

  1. publicclass Test implements Runnable    
  2. {    
  3.     privateint instance_i;//例項變數  
  4.     publicvoid run()    
  5.     {    
  6.         instance_i = 4;    
  7.         System.out.println("[" + Thread.currentThread().getName()    
  8.                 + "]獲取instance_i 的值:" + instance_i);    
  9.         instance_i = 10;    
  10.         System.out.println("[" + Thread.currentThread().getName()    
  11.                 + "]獲取instance_i*2的值:" + instance_i * 2);    
  12.     }    
  13.     publicstaticvoid main(String[] args)    
  14.     {    
  15.         Test t = new Test();    
  16.         //啟動儘量多的執行緒才能很容易的模擬問題   
  17.         for (int i = 0; i < 3000; i++)    
  18.         {    
  19.             //每個執行緒對在物件t中執行,模擬單例情況  
  20.             new Thread(t, "執行緒" + i).start();    
  21.         }    
  22.     }    
  23. }    

按照本文開頭的分析,猶如靜態變數那樣,每個執行緒都在修改同一個物件的例項變數,肯定會出現執行緒安全問題。

寫道

[執行緒66]獲取instance_i 的值:10 
[執行緒33]獲取instance_i*2的值:20 
[執行緒67]獲取instance_i 的值:4 
[執行緒34]獲取instance_i*2的值:8 
[執行緒35]獲取instance_i*2的值:20 
[執行緒68]獲取instance_i 的值:4

看紅色字型,可知單例情況下,例項變數執行緒非安全

將new Thread(t, "執行緒" + i).start();改成new Thread(new Test(), "執行緒" + i).start();模擬非單例情況,會發現不存線上程安全問題。

 三、區域性變數

執行緒安全問題模擬:

----------------------------------------------------------------------------------

  1. publicclass Test implements Runnable    
  2. {    
  3.     publicvoid run()    
  4.     {    
  5.         int local_i = 4;    
  6.         System.out.println("[" + Thread.currentThread().getName()    
  7.                 + "]獲取local_i 的值:" + local_i);    
  8.         local_i = 10;    
  9.         System.out.println("[" + Thread.currentThread().getName()    
  10.                 + "]獲取local_i*2的值:" + local_i * 2);    
  11.     }    
  12.     publicstaticvoid main(String[] args)    
  13.     {    
  14.         Test t = new Test();    
  15.         //啟動儘量多的執行緒才能很容易的模擬問題  
  16.         for (int i = 0; i < 3000; i++)    
  17.         {    
  18.             //每個執行緒對在物件t中執行,模擬單例情況   
  19.             new Thread(t, "執行緒" + i).start();    
  20.         }    
  21.     }    
  22. }    

控制檯沒有出現異常資料。

四、靜態方法 是否是執行緒安全的:

先看一個類

  1. publicclass  Test{  
  2. publicstatic  String hello(String str){  
  3.     String tmp="";  
  4.     tmp  =  tmp+str;  
  5.    return tmp;  
  6. }  
  7. }  

hello方法會不會有多執行緒安全問題呢?不會!

靜態方法如果沒有使用靜態變數,則沒有執行緒安全問題。

為什麼呢?因為靜態方法內宣告的變數,每個執行緒呼叫時,都會新建立一份,而不會共用一個儲存單元。比如這裡的tmp每個執行緒都會建立自己的一份,因此不會有執行緒安全問題。

注意,靜態成員變數,由於是在類載入時佔用一個儲存區,每個執行緒都是共用這個儲存區的,所以如果在靜態方法裡使用了靜態成員變數,這就會有執行緒安全問題。即只要方法內含有靜態成員變數,就是非執行緒安全的,(實際上歸根到底還是變數的執行緒安全問題~)。