Effective Java 筆記(十二)

來源:互聯網
上載者:User

相關讀書筆記列表

NO.48 對共用可變資料的同步訪問

  同步,不僅可以阻止一個線程看到對象處於不一致的狀態中,它還可以保證通過一系列看似順序執行的狀態轉變序列,對象從一種一致的狀態變遷到另一種一致的狀態。

  synchronized關鍵字可以保證在同一時刻,只有一個線程在執行一條語句,或者一段代碼塊。java語言保證讀或寫一個變數是原子的,除非這個變數的類型是long或double.

  java的記憶體模型決定,為了線上程之間可靠地通訊,以及為了互斥訪問,對原子資料的讀寫進行同步是需要的。看一個可怕的例子:
//Broken - require synchronization!<br />private static int nextSerialNumber=0;<br />public static int generateSerialNumber(){<br /> return nextSerialNumber++;<br />}

  對其改進,只需要在generateSerialNumber的聲明中增加synchronized修飾符即可。

  為了終止一個線程,一種推薦的做法是讓線程輪詢某個域,該域的值如果發生變化,就表明此線程就應該終止自己。下面的例子就是這個思路,但在同步出了問題。
//Broken - requires synchronization<br />public class StoppableThread extends Thread{<br />private boolean stopRequested=false;<br />public void run(){<br /> boolean done=false;<br /> while(!stopRequested && !done){<br /> ...//do what needs to be done in the thread<br /> }<br />}<br />public void requestStop(){<br /> stopRequested=true;<br /> }<br />}

  對其改進如下:
//Properly synchronized cooperative thread temination<br />public class StoppableThread extends Thread{<br />private boolean stopRequested=false;<br />public void run(){<br /> boolean done=false;<br /> while(!stopRequested() && !done){<br /> ...//do what needs to be done in the thread<br /> }<br />}<br />public synchronized void requestStop(){<br /> stopRequested=true;<br />}<br />private synchronized boolean stopRequested(){<br /> return stopRequested;<br /> }<br />}

  另一種改進是,將stopRequested聲明為volatile,則同步可以省略。

  再來看延遲初始設定(lazy initialization)問題,雙重訪問模式並不一定都能正常工作,除非被共用的變數包含一個原語值。看例子:
//The double-check idion fro lazy initialization - broken!<br />private static Foo foo=null;<br />public static Foo getFoo(){<br /> if (foo==null){<br /> synchronized(Foo.class){<br /> if(foo==null)foo=new Foo();<br /> }<br /> }<br /> return foo;<br />}

  最容易的修改是省去延遲初始設定:
//normal static initialization (not lazy)<br />private static finall Foo foo=new Foo();<br />public static Foo getFoo(){<br /> return foo;<br />}

  或者使用正確的同步方法,但可能增加少許的同步開銷:
//properly synchronized lazy initialization<br />private static Foo foo=null;<br />public static synchronized Foo getFoo(){<br /> if(foo==null)foo=new Foo();<br /> return foo;<br />}

  按需初始化容器模式也不錯,但是它只能用於靜態域,不能用於執行個體域。
//The initialize-on-demand holder class idiom<br />private static class FooHolder(){<br /> static final Foo foo=new Foo();<br />}<br />public static Foo getFoo(){ return FooHolder.foo;}

  簡而言之,無論何時當多個線程共用可變資料的時候,每個讀或寫資料的線程必須獲得一把鎖。如果沒有同步,則一個線程所做的修改就無法保證被另一個線程所觀察到。

NO.51 不要依賴於線程調度器

  不能讓應用程式的正確性依賴於線程調度器。否則,結果得到的應用程式既不健壯也不具有可移植性。作為一個推論,不要依賴Thread.yield或者線程優先順序。這些設施都只是影響到調度器,它們可以被用來提高一個已經能夠正常工作的系統的服務品質,但永遠不應用來“修正”一個原本並不能工作的程式。

  編寫健壯的、響應良好的、可移植的多線程應用程式的最好辦法是,儘可能確保在任何給定時刻只有少量的可運行線程。這種辦法採用的主要技術是,讓每個線程做少量的工作,然後使用Object.Wait等待某個條件發生,或者使用Thread.sleep睡眠一段時間。

NO.52 執行緒安全性的文檔化

  每個類都應該清楚地在文檔中說明它的安全執行緒屬性。在一個方法的聲明中出現synchronized修飾符,這是一個實現細節,並不是匯出的API文檔的一部分。

  一個類為了可被多個安全執行緒地使用,必須在文檔中清楚地說明它所支援的執行緒安全性層級。

    非可變性(immutable)-這個類的執行個體對於其它客戶而言是不變的,不需要外部的同步。參見13條。

    線程程安全的(thread-safe)-這個類的執行個體是可變的,但是所有的地方都包含足夠的同步手段,這些執行個體可以被並發使用無需外部同步。

    有條件的安全執行緒(conditionally thread-safe)-這個類(或關聯的類)包含有某些方法,它們必須被順序調用,而不能受到其它線程的幹擾,除此之外,這種安全執行緒層級與上一種情形相同。為了消除被其他線程幹擾的可能性,客戶在執行此方法序列期間,必須獲得一把適當的鎖。如HashTable或Vector,它們的迭代器要求外部同步。如:
Hashtable h=...;<br />synchronized(h){<br /> for(Enumeration e=h.keys();e.hasMoreElements();)<br /> f(e.nextElement());<br />}

    線程相容的(thread-compatible)-在每個方法調用的外圍使用外部同步,此時這個類的執行個體可以被安全的並發使用。如ArrayList或HashMap

    線程對立的(thread-hostile)這個類不能安全地被多個線程並發使用,即使所有的方法調用都被外部同步包圍。通常情況下,線程對立的根源在於,這個類的方法要修改待用資料,而這些待用資料可能會影響到其它的線程。

  對於有條件的安全執行緒類,在文檔中指明“為了允許方法調用序列以原子方式執行,哪一個對象應被鎖住”.

NO.53 避免使用線程組

  除了線程、鎖和監視器之外,線程系統還提供了一個基本的抽象,即線程組(thread-group)。然而線程組並沒有提供太多有用的功能。

  一個例外是,當線程組中的一個線程拋出一個未被捕獲的異常時,ThreadGroup.uncaughtException方法會被自動調用。“執行環境”使用這個方法,以便用適當的方式來響應未被捕獲的異常。

NO.54 保護性地編寫readObject方法

  編寫一個類的readObject方法,相當於編寫一個公有的建構函式,無論給它傳遞一個什麼樣的位元組流,它都必須產生一個有效執行個體。下面是縮寫健壯的readObject方法的指導原則:

①對於對象參考網域必須保持為私人的類,對“將被儲存到這些域中的對象”進行保護性拷貝。非可變類的可變組件就屬於這一類別。

②對於具有約束條件的類,一定要檢查約束條件是否滿足,如果不滿足的話,則拋出一個InvalidObjectException異常。這些檢查應跟在所有的保護性拷貝之後。

③如果在對象圖被還原序列化之後,整個對象圖必須都是有效,則應該使用ObjectInputValidation介面。

④無論是直接方式還是間接方式,都不要調用類中可被改寫的方法。

⑤readResolve方法有可能取被用來替代保護性的readObject方法。

  不嚴格地說,readObject是一個“用位元組流作為唯一參數”的建構函式。當面對一個人工偽造的位元組流的時候,readObject產生的對象會違反它所屬的類的約束條件。初步的方法,是在readObject方法進行約束性檢查,如下例:
private void readObject(OjbectInputStream s) throws IOException, ClassNotFoundException{<br /> s.defaultReadObject();<br /> //Check that our invariants are satisfied<br /> if(start.compareTo(end)>0) throw new InvalidObjectException(start+" after "+ end);<br />}

  對上述的防範仍可進行攻擊:偽造一個位元組流,這個位元組流以一個有效Period執行個體所產生的位元組流作為開始,然後附加上兩個額外的引用,指向 Period執行個體中的兩個內部私人Date域,攻擊者通過引用攻擊內部域。所以,當一個對象被還原序列化的時候,對於客戶不應該擁有的對象引用,如果哪個域包含了這樣的對象引用,則必須要做保護性拷貝,這是非常重要的。如下例:
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException{<br /> s.defaultReadObject();<br /> start=new Date(start.getTime());<br /> end=new Date(end.getTime());<br /> if(start.compareTo(end)>0) throw new InvlaidObjectException(start+" after "+end);<br />}

 
NO.57 必要時提供一個readResolve方法

  無論是 singleton,或是其他執行個體受控(instance-controlled)的類,必須使用readResolve方法來保護“執行個體-控制的約束 ”。從本質上來講,readResovle方法把一個readObject方法從一個事實上的公有建構函式變成一個事實上的公有靜態工廠。對於那些禁止包外繼承的類而言,readResolve方法作為保護性的readObject方法的一種替代,也是非常有用的。

  如下sigleton類:
public class Elvis{<br /> public static final Elvis INSTANCE = new Elvis();<br /> private Elvis(){<br /> ...<br /> }<br /> ...//remainder omitted<br />}

  如果Elvis執行個體序列化介面,則下面的readResolve方法足以保證它的sigleton屬性。
private Object readResolve() throws ObjectStreamException{<br /> //return the one true elvis and let the GC take care of the Elvis impersonator<br /> return INSTANCE;<br />}

  不僅僅對於singleton對象是必要的,readResolve方法對於所有其它的執行個體受控類(如型別安全枚舉類型)也是必需的。

  readResolve方法的第二個用法是,就像在第56條建議的那樣,作為保護性的readObject方法的一種保守的替代選擇。此時,第56條中的readObject方法可以下例的例子替代:
//the defensive readResolve idiom<br />private Object readResolve() throws ObjectStreamException(){<br /> return new Period(start,end);<br />}

  對於那些允許繼承的類,readResolve方法可能無法替代保護性的readObject方法。如果超類的readResolve方法是final 的,則使得子類執行個體無法被正常地還原序列化。如果超類的readResolve方法是可改寫的,則惡意的子類可能會用一個方法改寫它,該方法返回一個受損的執行個體。

 

      結束語:花了好幾個月斷斷續續地把這本書看完了,期間做了好幾個項目,忙了一段時間,看書的進度有些拖延,現在看之前整理的筆記都有點遺忘了,不過沒關係,以後可以返回去看看,溫故而知新嘛。。。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.