java逸出_Java并发编程 - 对象的共享
編寫正確的并發程序,關鍵問題在于:在訪問共享的可變狀態時需要進行正確的管理。同步代碼塊和同步方法可以確保以原子的方式執行操作,同步還有另一個重要的方面:內存可見性。
可見性
為了確保多個線程之間對內存寫入操作的可見性,必須使用同步機制。public?class?NoVisibility?{
private?static?boolean?ready;
private?static?int?number;
private?static?class?ReaderThread?extends?Thread?{
public?void?run()?{
while?(!ready)
Thread.yield();
System.out.println(number);
}
}
public?static?void?main(String[]?args)?{
new?ReaderThread().start();
number?=?42;
ready?=?true;
}
}
NoVisibility可能會持續循環下,因為讀線程可能永遠都看不到ready的值。一種更奇怪的現象是,NoVisibility可能會輸出0,因為讀線程可能看到了寫入ready的值,卻沒有看到之后寫入number的值,這種現象被稱為"重排序"。
重排序:這看上去似乎是一種失敗的設計,但卻能使JVM充分地利用現代多核處理器的強大性能。例如,在缺少同步的情況下,Java內存模型允許編譯器對操作順序進行重排序,并將數值緩存在寄存器中。此外,它還允許CPU對操作順序進行重排序,并將數值緩存在處理器特定的緩存中。
在沒有同步的情況下,編譯器、處理器以及運行時等都可能對操作的執行順序進行一些意想不到的調整。在缺乏足夠同步的多線程程序中,要想對內存操作的執行順序進行判斷,幾乎無法得出正確的結論。非原子的64位操作
當線程在沒有同步的情況下讀取變量時,可能會得到一個失效值,但至少這個值是由之前某個線程設置的值,而不是一個隨機值。這種安全性保證也被稱為最低安全性。最低安全性適用于絕大多數變量,但是存在一個例外:非volatile類型的64位數值變量(double和long)。Java內存模型要求,變量的讀取操作和寫入操作都必須是原子操作,但對于非volatile類型的double和long變量,JVM允許將64位的讀操作或寫操作分解為兩個32位的操作。
加鎖與可見性
加鎖的含義不僅僅局限于互斥行為,還包括內存可見性。為了確保所有縣城都能看到共享變量的最新值,所有執行讀操作或者寫操作的線程都必須在同一個鎖上同步。
Volatile變量
當把變量時聲明為volatile類型后,編譯器與運行時都會注意到這個變量時共享的,因此不會將該變量上的操作與其他內存操作一起重排序。在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞。volatile變量是一種比synchronized關鍵字更輕量級的同步機制。
volatile變量通常用做某個操作完成、發生中斷或者狀態的標識。
加鎖機制即可以確??梢娦杂挚梢源_保原子性,而volatile變量只能確??梢娦?。
發布與逸出
"發布"一個對象的意思是指,使對象能夠在當前作用域之外的代碼中使用。當某個不應該發布的對象發布時,這種情況就被稱為逸出。
發布對象的最簡單方法是將對象的引用保存到一個公有的靜態變量中,以便任何類型和線程都能看到該對象。class?UnsafeStates?{
private?class?String[]?states?=?new?String[]?{
"AK",?"AL"?...
};
public?String[]?getStates()?{?return?states;?}
}
如果按照上述方式來發布states,就會出現問題,因為任何調用者都能修改這個數組的內容。當發布一個對象時,在該對象的非私有域中引用的所有對象同樣會被發布。一般來說,如果一個已經發布的對象能夠通過非私有的變量引用和方法調用到達其他的對象,那么這些對象也會被發布。
線程封閉
如果僅在單線程內訪問數據,就不需要同步。這種技術被稱為線程封閉。當某個對象封裝在一個線程中時,這種用法將自動實現線程安全性,即使被封閉的對象本身不是線程安全的。
線程封閉技術的一種常見應用是JDBC的Connection對象。JDBC規范不要求Connection對象必須是線程安全的,線程從連接池中獲得一個Connection對象,并且用該對象來處理請求,使用完后再將對象返還給連接池。由于大多數請求都是由單個線程采用同步的方式來處理,并且在Connection對象返回之前,連接池不會把它再分給其他線程,因此,這種連接管理模式在處理請求時隱含地把Connection對象封裝在線程中。
Ad-hoc線程封閉
Ad-hoc線程封閉是指,維護線程封閉性的職責完全由程序實現來承擔。
棧封閉
棧封閉是線程封閉的一種特例,在棧封閉中,只能通過局部變量才能訪問對象。局部變量的固有屬性之一就是封閉在執行線程中。它們位于執行線程的棧中,其他線程無法訪問這個棧。
ThreadLocal類
ThreadLocal提供了get與set等訪問接口或方法,這些方法為每個使用該變量的線程都存有一份獨立的副本,因此get總是返回由當前線程在調用set時設置的最新值。ThreadLocal對象通常用于防止對可變對象的單實例變量或者全局變量進行共享。
當某個頻繁執行的操作需要一個臨時變量,例如一個緩沖區,而同時又希望避免在每次執行時都重新分配該臨時對象,就可以使用這項技術。
不變性
如果某個對象在被創建后其狀態就不能被修改,那么這個對象就稱為不可變對象。不可變對象很簡單。它們只有一種狀態,并且該狀態由構造函數來控制。
當滿足一下條件時,對象才是不可變的:
對象創建以后其狀態就不能修改
對象的所有域都是final類型
對象時正確創建的(在對象的創建期間,this引用沒有逸出)@Immutable
public?final?class?ThreadStooges?{
private?final?Set?stooges?=?new?HashSet();
public?ThreeStooges?{
stooges.add("Moe");
stooges.add("Larry");
stooges.add("Curly");
}
public?boolean?isStooge(String?name)?{
return?stooges.contains(name);
}
}
stoogegs是一個final類型的引用變量,因此所有的對象狀態都通過一個final域來訪問。最后一個要求是"正確地構造對象",在Set對象構造完成后無法對其進行修改。
安全發布
如果確保對象不被發布,例如讓對象封閉在線程或另一個對象的內部。//?不安全發布
public?Holder?holder;
public?void?initialize()?{
hodler?=?new?Holder(42);
}
由于存在可見性問題,其他線程看到的Holder對象將處于不一致的狀態,即便在該對象的構造函數中已經正確地構造了不變性條件。這種不正確的發布導致其他線程看到尚未創建完成的對象。
安全發布的常用模式
可變對象必須通過安全的方式來發布,這通常意味著在發布和使用該對象的線程時都必須使用同步。
要安全地發布一個對象,對象的引用以及對象的狀態必須同時對其他線程可見。一個正確構造的對象可以通過以下方式來安全地發布:
在靜態初始化函數中初始化一個對象引用
將對象的引用保存到volatile類型的域或者AtomicReferance對象中
將對象的引用保存到某個正確構建對象的final類型域中
將對象的引用保存到一個由鎖保護的域中
事實不可變對象
如果對象在發布后不會被修改,那么對于其他在沒有額外同步的情況下安全地訪問這些對象的線程來說,安全發布是足夠的。當對象的引用對所有訪問該對象的線程可見時,對象發布時的狀態對于所有線程也將是可見的,并且如果對象狀態不會再改變,那么這就足以確保任何訪問都是安全的。
如果從技術上來看是可變的,但其狀態在發布后不會再改變,那么把這種對象稱為"事實不可變對象"。
例如,Date本身是可變的,如果Date對象的值放入Map后就不會改變,那么synchronizedMap中的同步機制就足以使Date值被安全的發布,并且在訪問這些Date值時不需要額外的同步。
可變對象
對于可變對象,不僅在發布對象時需要使用同步,而且在每次對象訪問時同樣血藥使用同步來確保后續修改操作的可見性。
對象的發布需要取決于它的可變性:
不可變對象可以通過任意機制來發布
事實不可變對象必須通過安全方式來發布
可變對象必須通過安全方式來發布,并且必須是線程安全的或者由某個鎖保護起來
安全地共享對象
在并發程序中使用和共享對象時,可以使用一些使用的策略,包括:
線程封閉。線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,并且只能由這個線程修改。
只讀共享。在沒有額外同步的情況下,共享的只讀對象可以由多個線程并發訪問,但任何線程都不能修改它。共享的只讀對象包括不可變對象和事實不可變對象。
線程安全共享。線程安全的對象在其內部實現同步,因此多個線程可以通過對象的公有接口來進行訪問而不需要進一步的同步。
保護對象。被保護的對象只能通過持有特定的鎖來訪問。保護對象包括封裝在其他線程安全對象中的對象,以及以發布的并且由某個特定鎖保護的對象。
轉載請并標注: “本文轉載自 linkedkeeper.com ”???著作權歸作者所有
總結
以上是生活随笔為你收集整理的java逸出_Java并发编程 - 对象的共享的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 存储引擎版本_mysql不
- 下一篇: java按照io流向基类_Java IO