天天看點

對象的共享(第三章)

在多線程程式中,我們不僅希望防止某個線程正在使用對象狀态而另一個線程在同時修改該狀态,而且希望確定當一個線程修改了對象狀态後,其他線程能夠看到發生的狀态變化。如果沒有同步,那麼這種情況就無法實作。

重排序:在沒有同步的情況下,編譯器、處理器以及運作時都可能對操作的執行順序進行一些意想不到的調整。在缺乏足夠同步的多線程程式中,要想對記憶體操作的執行順序進行判斷,幾乎無法得出正确地結論。

在上面代碼中,結果可能會輸出0。因為在缺少同步的情況下,Java記憶體模型允許編譯器對操作順序進行重排序,并将數值緩存在寄存器中,它還允許CPU對操作順序進行重排序,并将數值緩存在特定的緩存中。

非原子類的64位操作

Java記憶體模型要求,變量的讀取操作和寫入操作都必須是原子操作,但對于非volatile類型的long和double變量,JVM允許将64位的讀操作或寫操作分解為兩個32位的操作,進而破壞了原子性,除非用關鍵字“volatile”來聲明它們或者使用鎖來保護它們。

内置鎖可以用于確定某個線程以一種可預測的方式來檢視另一個線程的執行結果。

volatile變量

Java提供了一種稍弱的同步機制,即volatile變量,用來確定将變量的更新操作通知到其他線程。當把變量聲明為volatile類型後,編譯器與jre都會注意到這個變量是共享的,是以不會将該變量上的操作與其他記憶體操作一起重排序。

但volatile并不會加鎖,是以也就不會産生阻塞行為。是以,volatile變量是一種比synchronized更輕量級的同步機制。

volatile變量通常用作某個操作完成、發生中斷或者正太改變的标志,在使用時應非常小心,例如,volatile的語義不足以確定遞增(++)操作的原子性。

使用volatile變量的時機:

對變量的寫入操作不依賴變量的目前值(避免競态條件),或者你能確定隻有單個線程更新變量的值

該變量不會與其他狀态變量一起納入不變性條件中

在通路變量時不需要加鎖

釋出(Publish)一個對象:使對象能夠在目前作用域之外的代碼中使用。

當釋出一個對象時,在該對象的非私有域中引用的所有對象同樣會被釋出

當釋出某個對象時,可能會間接釋出其他對象,如釋出一個List,包含在這個List中的對象也會被釋出,如下代碼所示

逸出(Escape):一個不該被釋出的對象被釋出

不要在構造過程中使this引用逸出

當且僅當對象的構造函數傳回時,對象才處于可預測的和一緻的狀态,是以,當對象從構造函數中釋出時(如傳回一個匿名内部類),隻是釋出了一個尚未構造完成的對象。這造成了不正确構造。

在構造函數中啟動一個線程

如果想在構造函數中注冊一個事件監聽器或啟動線程,可以使用一個私有的構造函數和一個公共的工廠方法

棧封閉:隻能通過局部變量通路對象

ThreadLocal類:這個類能使線程中的某個值與儲存值的對象關聯起來。ThreadLocal類提供了get與set等方法,這些方法為每個使用該變量的線程都存有一份獨立的副本,是以get總是傳回目前執行線程在調用set時設定的最新值

滿足同步需求的另一種方法是使用不可變對象。

不可變對象總是線程安全的。

要安全地釋出一個對象,對象的引用以及對象的狀态必須同時對其他線程可見。

通常,要釋出一個靜态構造的對象,最簡單和最安全的方式是使用靜态的初始化器:

static Holder holder = new Holder(42);