1. 程式人生 > >JAVA Volatile與Transient關鍵字

JAVA Volatile與Transient關鍵字

Volatile修飾的成員變數在每次被執行緒訪問時,都強迫從主記憶體中重讀該成員變數的值。而且,當成員變數發生變化時,強迫執行緒將變化值回寫到主記憶體。這樣在任何時刻,兩個不同的執行緒總是看到某個成員變數的同一個值。   
  
Java語言規範中指出:為了獲得最佳速度,允許執行緒儲存共享成員變數的私有拷貝,而且只當執行緒進入或者離開同步程式碼塊時才與共享成員變數的原始值對比。   
  
這樣當多個執行緒同時與某個物件互動時,就必須要注意到要讓執行緒及時的得到共享成員變數的變化。   
  
而volatile關鍵字就是提示VM:對於這個成員變數不能儲存它的私有拷貝,而應直接與共享成員變數互動。   
  
使用建議:在兩個或者更多的執行緒訪問的成員變數上使用volatile。當要訪問的變數已在synchronized程式碼塊中,或者為常量時,不必使用。   
  
由於使用volatile遮蔽掉了VM中必要的程式碼優化,所以在效率上比較低,因此一定在必要時才使用此關鍵字。   
  
java關鍵字Transient   
  
轉自http:
//horst.sun.blog.163.com/blog/static/348849612007614494492/   
  
翻譯自http:
//www.devx.com/tips/Tip/13726。   
  
Java的serialization提供了一種持久化物件例項的機制。當持久化物件時,可能有一個特殊的物件資料成員,我們不想   
用serialization機制來儲存它。為了在一個特定物件的一個域上關閉serialization,可以在這個域前加上關鍵字transient。   
transient是Java語言的關鍵字,用來表示一個域不是該物件序列化的一部分。當一個物件被序列化的時候,transient型變數的值不包括在序列化的表示中,然而非transient型的變數是被包括進去的。  
注意static變數也是可以序列化的 
  
首先,讓我們看一些Java serialization的程式碼:   
publicclass LoggingInfo implements java.io.Serializable   
{   
    
private Date loggingDate =new Date();   
    
private String uid;   
    
privatetransient String pwd;   
      
    LoggingInfo(String user, String password)   
    
{   
        uid 
= user;   
        pwd 
= password;   
    }
   
    
public String toString()   
    
{   
        String password
=null;   
        
if(pwd ==null)   
        
{   
        password 
="NOT SET";   
        }
   
        
else  
        
{   
            password 
= pwd;   
        }
   
        
return"logon info: \n   "+"user: "+ uid +   
            
"\n   logging date : "+ loggingDate.toString() +   
            
"\n   password: "+ password;   
    }
   
}
   
  
現在我們建立一個這個類的例項,並且序列化(serialize)它 ,然後將這個序列化物件寫如磁碟。   
  
LoggingInfo logInfo 
=new LoggingInfo("MIKE""MECHANICS");   
System.out.println(logInfo.toString());   
try  
{   
   ObjectOutputStream o 
=new ObjectOutputStream(   
                
new FileOutputStream("logInfo.out"));   
   o.writeObject(logInfo);   
   o.close();   
}
   
catch(Exception e) {//deal with exception}   
  
To read the object back, we can write   
  
try  
{   
   ObjectInputStream in 
=new ObjectInputStream(   
                
new FileInputStream("logInfo.out"));   
   LoggingInfo logInfo 
= (LoggingInfo)in.readObject();   
   System.out.println(logInfo.toString());   
}
   
catch(Exception e) {//deal with exception}   
  
如果我們執行這段程式碼,我們會注意到從磁碟中讀回(read——back (de
-serializing))的物件列印password為"NOT SET"。這是當我們定義pwd域為transient時,所期望的正確結果。   
現在,讓我們來看一下粗心對待transient域可能引起的潛在問題。假設我們修改了類定義,提供給transient域一個預設值,   
程式碼如下:   
  
publicclass GuestLoggingInfo implements java.io.Serializable   
{   
    
private Date loggingDate =new Date();   
    
private String uid;   
    
privatetransient String pwd;   
      
    GuestLoggingInfo()   
    
{   
        uid 
="guest";   
        pwd 
="guest";   
    }
   
    
public String toString()   
    
{   
        
//same as above   
     }
   
}
   
現在,如果我們穿行化GuestLoggingInfo的一個例項,將它寫入磁碟,並且再將它從磁碟中讀出,我們仍然看到讀回的物件列印password 為 
"NOT SET"。當從磁碟中讀出某個類的例項時,實際上並不會執行這個類的建構函式,   
而是載入了一個該類物件的持久化狀態,並將這個狀態賦值給該類的另一個物件。