Java Volatile transient

Java Volatile transient
Java Volatile說明
http://blog.csdn.net/blueheart20/archive/2007/04/29/1591874.aspx
在Java中設置變量值的操作,除了long和double類型的變量外都是原子操作,也就是說,對於變量值的簡單讀寫操作沒有必要進行同步。這在 JVM 1.2之前,Java的內存模型實現總是從主存讀取變量,是不需要進行特別的注意的。而隨着JVM的成熟和優化,現在在多線程環境下volatile關鍵字的使用變得非常重要。在當前的Java內存模型下,線程可以把變量保存在本地內存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成數據的不一致。要解決這個問題,只需要像在本程序中的這樣,把該變量聲明爲volatile(不穩定的)即可,這就指示JVM,這個變量是不穩定的,每次使用它都到主存中進行讀取。一般說來,多任務環境下各任務間共享的標誌都應該加volatile修飾。
在實際工作中很少有用到volatile這個關鍵字,今天在看一段開源代碼時碰到它,查了一下它的用法 :
用在多線程,同步變量 線程爲了提高效率,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的是B。只在某些動作時才進行A和B的同步。因此存在A和B不一致的情況。volatile就是用來避免這種情況的。
volatile告訴jvm, 它所修飾的變量不保留拷貝,直接訪問主內存中的(也就是上面說的A)


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1591874

http://blog.csdn.net/caoi/archive/2006/03/28/640939.aspx

談談Java關鍵字transient和volatile。
transient
把一個對象的表示轉化爲字節流的過程稱爲串行化serialization,從字節流中把對象重建出來稱爲反串行化deserialization,transient 爲不應被串行化的數據提供了一個語言級的標記數據方法。
對象的序列化(serialization)非常影響I/O的性能,儘量少用。對不需序列化的類的域使用transient關鍵字,以減少序列化的數據量。
參考:Serializable(中文,英文)
Java Serialization Example
Serializable java序列化可能帶來的問題
空接口的使用(給JAVA設計開發新手的一些建議和意見)
volatile
在Java中設置變量值的操作,除了long和double類型的變量外都是原子操作,也就是說,對於變量值的簡單讀寫操作沒有必要進行同步。這在JVM 1.2之前,Java的內存模型實現總是從主存讀取變量,是不需要進行特別的注意的。而隨着JVM的成熟和優化,現在在多線程環境下volatile關鍵字的使用變得非常重要。在當前的Java內存模型下,線程可以把變量保存在本地內存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成數據的不一致。要解決這個問題,只需要像在本程序中的這樣,把該變量聲明爲volatile(不穩定的)即可,這就指示JVM,這個變量是不穩定的,每次使用它都到主存中進行讀取。一般說來,多任務環境下各任務間共享的標誌都應該加volatile修飾。(來源和其他參考 )

http://blog.csdn.net/ai92/archive/2005/03/08/315183.aspx
初探關鍵字volatile
第一次接觸到關鍵字volatile,不知爲何物,只是模糊的記得java關鍵字裏面好像有它。查了些資料,整理如下:
Volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。而且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。
Java語言規範中指出:爲了獲得最佳速度,允許線程保存共享成員變量的私有拷貝,而且只當線程進入或者離開同步代碼塊時才與共享成員變量的原始值對比。
這樣當多個線程同時與某個對象交互時,就必須要注意到要讓線程及時的得到共享成員變量的變化。
而volatile關鍵字就是提示VM:對於這個成員變量不能保存它的私有拷貝,而應直接與共享成員變量交互。
使用建議:在兩個或者更多的線程訪問的成員變量上使用volatile。當要訪問的變量已在synchronized代碼塊中,或者爲常量時,不必使用。
由於使用volatile屏蔽掉了VM中必要的代碼優化,所以在效率上比較低,因此一定在必要時才使用此關鍵字。

http://blog.csdn.net/majorboy/archive/2005/09/09/475811.aspx
關於volatile和synchronized
這個可能是最好的對比volatile和synchronized作用的文章了。volatile是一個變量修飾符,而synchronized是一個方法或塊的修飾符。所以我們使用這兩種關鍵字來指定三種簡單的存取變量的方式。
int i1; int geti1() {return i1;}
volatile int i2; int geti2() {return i2;}
int i3; synchronized int geti3() {return i3;}
geti1()在當前線程中立即獲取在i1變量中的值。線程可以獲得變量的本地拷貝,而所獲得的變量的值並不一定與其他線程所獲得的值相同。特別是,如果其他的線程修改了i1的值,那麼當前線程獲得的i1的值可能與修改後的值有所差別。實際上,Java有一種主內存的機制,使用一個主內存來保存變量當前的正確的值。線程將變量的值拷貝到自己獨立的內存中,而這些線程的內存拷貝可能與主內存中的值不同。所以實際當中可能發生這樣的情況,在主內存中i1的值爲1,線程1和線程2都更改了i1,但是卻沒把更新的值傳回給主內存或其他線程中,那麼可能在線程1中i1的值爲2,線程2中i1的值卻爲3。
另一方面,geti2()可以有效的從主內存中獲取i2的值。一個volatile類型的變量不允許線程從主內存中將變量的值拷貝到自己的存儲空間。因此,一個聲明爲volatile類型的變量將在所有的線程中同步的獲得數據,不論你在任何線程中更改了變量,其他的線程將立即得到同樣的結果。由於線程存取或更改自己的數據拷貝有更高的效率,所以volatile類型變量在性能上有所消耗。
那麼如果volatile變量已經可以使數據在線程間同步,那麼synchronizes用來幹什麼呢?兩者有兩方面的不同。首先,synchronized獲取和釋放由監聽器控制的鎖,如果兩個線程都使用一個監聽器(即相同對象鎖),那麼監聽器可以強制在一個時刻只有一個線程能處理代碼塊,這是最一般的同步。另外,synchronized還能使內存同步。在實際當中,synchronized使得所有的線程內存與主內存相同步。所以geti3()的執行過程如下:
1. 線程從監聽器獲取對象的鎖。(這裏假設監聽器非鎖,否則線程只有等到監聽器解鎖才能獲取對象鎖)
2. 線程內存更新所有的變量,也就是說他將讀取主內存中的變量使自己的變量保證有效。(JVM會使用一個“髒”標誌來最優化過程,使得僅僅具有“髒”標誌變量被更新。詳細的情況查詢JAVA規範的17.9)
3. 代碼塊被執行(在這個例子中,設置返回值爲剛剛從主內存重置的i3當前的值。)
4. 任何變量的變更將被寫回到主內存中。但是這個例子中geti3()沒有什麼變化。
5. 線程釋放對象的鎖給監聽器。
所以volatile只能在線程內存和主內存之間同步一個變量的值,而synchronized則同步在線程內存和主內存之間的所有變量的值,並且通過鎖住和釋放監聽器來實現。顯然,synchronized在性能上將比volatile更加有所消耗。

破除java神話之三:原子操作都是線程安全的
java中原子操作是線程安全的論調經常被提到。根據定義,原子操作是不會被打斷地的操作,因此被認爲是線程安全的。實際上有一些原子操作不一定是線程安全的。
這個問題出現的原因是儘量減少在代碼中同步關鍵字。同步會損害性能,雖然這個損失因JVM不同而不同。另外,在現代的JVM中,同步的性能正在逐步提高。儘管如此,使用同步仍然是有性能代價的,並且程序員永遠會盡力提高他們的代碼的效率,因此這個問題就延續了下來。
在java 中,32位或者更少位數的賦值是原子的。在一個32位的硬件平臺上,除了double和long型的其它原始類型通常都是使用32位進行表示,而 double和long通常使用64位表示。另外,對象引用使用本機指針實現,通常也是32位的。對這些32位的類型的操作是原子的。
這些原始類型通常使用32位或者64位表示,這又引入了另一個小小的神話:原始類型的大小是由語言保證的。這是不對的。java語言保證的是原始類型的表數範圍而非JVM中的存儲大小。因此,int型總是有相同的表數範圍。在一個JVM上可能使用32位實現,而在另一個JVM上可能是64位的。在此再次強調:在所有平臺上被保證的是表數範圍,32位以及更小的值的操作是原子的。
那麼,原子操作在什麼情況下不是線程安全的?主要的一點是他們也許確實是線程安全的,但是這沒有被保證!java線程允許線程在自己的內存區保存變量的副本。允許線程使用本地的私有拷貝進行工作而非每次都使用主存的值是爲了提高性能。考慮下面的類:

class RealTimeClock
{
private int clkID;
public int clockID()
{
return clkID;
}
public void setClockID(int id)
{
clkID = id;
}
//...
}
現在考慮RealTimeClock的一個實例以及兩個線程同時調用setClockID和clockID,併發生以下的事件序列:
T1 調用setClockID(5)
T1將5放入自己的私有工作內存
T2調用setClockID(10)
T2將10放入自己的私有工作內存
T1調用clockID,它返回5
5是從T1的私有工作內存返回的
對clockI的調用應該返回10,因爲這是被T2設置的,然而返回的是5,因爲讀寫操作是對私有工作內存的而非主存。賦值操作當然是原子的,但是因爲JVM允許這種行爲,因此線程安全不是一定的,同時,JVM的這種行爲也不是被保證的。

兩個線程擁有自己的私有拷貝而不和主存一致。如果這種行爲出現,那麼私有本機變量和主存一致必須在以下兩個條件下:
1、變量使用volatile聲明
2、被訪問的變量處於同步方法或者同步塊中
如果變量被聲明爲volatile,在每次訪問時都會和主存一致。這個一致性是由java語言保證的,並且是原子的,即使是64位的值。(注意很多JVM沒有正確的實現volatile關鍵字。你可以在www.javasoft.com找到更多的信息。)另外,如果變量在同步方法或者同步塊中被訪問,當在方法或者塊的入口處獲得鎖以及方法或者塊退出時釋放鎖是變量被同步。
使用任何一種方法都可以保證ClockID返回10,也就是正確的值。變量訪問的頻度不同則你的選擇的性能不同。如果你更新很多變量,那麼使用volatile可能比使用同步更慢。記住,如果變量被聲明爲volatile,那麼在每次訪問時都會和主存一致。與此對照,使用同步時,變量只在獲得鎖和釋放鎖的時候和主存一致。但是同步使得代碼有較少的併發性。
如果你更新很多變量並且不想有每次訪問都和主存進行同步的損失或者你因爲其它的原因想排除併發性時可以考慮使用同步。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章