天天看點

建議:避免過度同步。

依據情況的不同,過度同步可能會導緻性能降低、死鎖,甚至不确定的行為。

為了避免活性失敗和安全性失敗,在一個被同步的方法或者代碼塊中,永遠不要放棄對用戶端的控制。換句話說,在一個被同步的區域内部,不要調用設計成要被覆寫的方法,或者是由用戶端以函數對象的形式提供的方法。從包含該同步區域的類的角度來看,這樣的方法是外來的。這個類不知道該方法會做什麼事情,也無法控制它。根據外來方法的作用,從同步區域中調用他會導緻異常、死鎖或者資料損壞。

假設當同步區域所保護的限制條件暫時無效時,你要從同步區域中調用一個外來方法。由于Java程式設計語言中的鎖是可重入的,這種調用不會死鎖。他會産生一個異常,因為調用線程已經有這個鎖了,是以當該線程試圖再次獲得該鎖時會成功,盡管概念上不相關的另一項操作正在該鎖保護的資料上進行着。這種失敗的後果可能是災難的。從本質上來說,這個鎖沒有盡到它的職責。可再入的鎖簡化了多線程的面向對象程式的構造,但是他們可能會将活性失敗變成安全性失敗。

事實上,要将外來方法的調用移出同步的代碼塊。還有一種更好地方法。自從Java 1.5發行版本以來,Java類庫就提供了一個并發集合,稱作CopyOnWriteArrayLIst,這是專門為此定制的。這是ArrayList的一種變種,通過重新拷貝整個底層數組,在這裡實作所有的寫操作。由于内部數組永遠不改動,是以疊代不需要鎖定,速度也非常快。如果大量使用,CopyOnWriteArrayList的性能将大受影響,但是對于觀察者清單來說卻是很好的,因為他們幾乎不改動,并且經常被周遊。

在同步區域之外被調用的外來方法被稱作“開放調用”。除了可以避免死鎖之外,開放調用還可以極大地增加并發性。外來方法的運作時間可能會任意長。如果在同步區域内調用外來方法,其他線程對受保護資源的通路就會遭到不必要的拒絕。

通常,你應該在同步區域内做盡可能少的工作。獲得鎖,檢查共享資料,根據需要轉換資料,然後放掉鎖。如果你必須要執行某個很耗時的動作,則應該設法把這個動作移到同步區域的外面。

雖然自從Java平台早期以來,同步的成本已經下降了,但更重要的是,永遠不要過度同步。在這個多核的時代,過度同步的實際成本并不是指擷取鎖所花費的CPU時間;而是指失去了并行的機會,以及因為需要確定每個核都有一個一緻的記憶體視圖而導緻的延遲。過度同步的另一項潛在開銷在于,他會限制VM優化代碼執行的能力。

如果一個可變的類要并發使用,應該使這個類變成是線程安全的,通過内部同步,你還可以獲得明顯比從外部鎖定整個對象更高的開發性。否則,就不要在内部同步。讓客戶在必要的時候從外部同步。在Java平台出現的早期,許多類都違背了這些指導方針。當你不确定的時候,就不要同步你的類,而是應該建立文檔,注明它不是線程安全的。

如果你的内部同步了類,就可以使用不同的方法來實作高并發性,例如分拆鎖、分離鎖和非阻塞并發控制。

如果方法修改了靜态域,那麼你也必須同步對這個域的通路,即使他往往隻用于單個線程。客戶要在這種方法上執行外部同步時不可能的,因為不可能保證其他不想關的客戶也會執行外部同步。

簡而言之,為了避免死鎖和資料破壞。千萬不要從同步區域内部調用外來方法。更為一般的講,要盡量限制同步區域内部的工作量。當你在設計一個可變類的時候,要考慮一下他們是否應該自己完成同步操作。在現在這個多核的時代,這比永遠不要過度同步來得更重要。隻有當你有足夠的理由一定要在内部同步類的時候,才應該這麼做,同時還應該将這個決定清楚地寫到文檔中。