Effective Java读书笔记一:并发
第66條:同步訪問共享的可變數據
關鍵字synchronized可以保證在同一時刻,只有一個線程可以執行某一個方法,或者某一個代碼塊。
同步不僅可以阻止一個線程看到對象處于不一致的狀態中,它還可以保證進入同步方法或者同步代碼塊的每個線程,都看到同一個鎖保護的之前的修改效果。
Volatile 變量可用于提供線程安全,但是只能應用于非常有限的一組用例:多個變量之間或者某個變量的當前值與修改后值之間沒有約束。對變量的寫操作不依賴于當前值,該變量沒有包含在具有其他變量的不變式中。不具有原子性。
多個線程共享可變數據的時候,每個讀或寫數據的線程都必須執行同步。如果沒有同步,就無法保證一個線程所做的修改可以被另一個線程獲知。如果需要線程之間的交互通信,而不需要互斥,volatile修飾符就是一種可以接受的形式,但需要正確的使用。
第67條:避免過度同步
依據情況不同,過度同步可能會導致性能降低,死鎖,甚至不確定的行為。
為了避免活性失敗和安全性失敗,在一個被同步的方法或代碼塊中,永遠不要放棄對客戶端的控制。換句話說,在一個被同步的區域內部,不要調用設計成要被覆蓋的方法,或者由客戶端以函數對象的形式提供的方法。
通常,你應該在同步區域內做盡量少的工作。
在這個多核的時代多度同步的實際成本并不是指獲得鎖所花費的CPu時間;而是指失去了并行地機會,以及因為需要確保每個核都有一個一致的內存視圖而導致的延遲。過度同步的另一項潛在開銷在于,他會限制Vm優化代碼的能力。
如果一個可變類要并發使用,應該使這個類編程線程安全的,通過內部同步,你還可以獲得,明顯比外部鎖定整個對象更高的并發性。否則,就不要在內部同步。讓客戶在必要的時候從外部同步。
反例:?
StringBuffer實例幾乎總是被用于單個線程中,而它們執行的卻是內部同步。為此,StringBuffer基本都由StringBuilder代替。
第68條:executor和task優先于線程
- 如果編寫的是小程序,或者輕載的服務器,使用Executor.newCachedThreadPool通常是個不錯的選擇。
- 在大負載的服務器中,最好使用Executor.newFixedThreadPool,它為你提供了一個包含固定線程數目的線程池,或者為了最大限度的控制它,就直接使用ThreadPoolExecutor類。
你不僅應該盡量不要編寫自己的工作隊列,而且還應該盡量不直接使用線程。現在的關鍵抽象不再是Thread了,它以前既充當工作單位,又是執行機制。工作單位和工作單位是分開的,現在的關鍵抽象是工作單元,稱作任務(task)。
任務有兩種:Runnable及其近親Callable(它與Runnable類似,但它會返回值)。執行任務的通用機制是executor service。
Executor FrameWork也有一個可以代替Java.util.Timer的東西,即ScheduledThreadPoolExecutor。
Timer只有一個線程來執行任務,如果timer唯一的線程拋出未被捕捉的異常,timer就會停止工作。而線程池executor支持多個線程,并且優雅的從拋出未受檢異常的任務中恢復。
第69條:并發工具優先于wait和notify
java.util.concurrent中更高級的工具分三類:Executor Framework,并發集合(Concurrent Collection)以及同步器(Synchronizer)。
并發集合為標準的集合接口提供了高性能的并發實現,這些實現在內部自己管理同步。因此,并發集合中不可能排除并發活動;將它鎖定沒有什么作用,只會使程序的速度變慢。
concurrent collections提供了標準容器的高性能并發實現.內部同步和互斥,外部使用,無需加鎖.
優先使用ConcurrentHashMap,而不是Collections.synchronizedMap或者Hashtable,且無需做同步操作.
有的concurrent collections提供了block操作接口,例如BlockingQueue,從中取數據的時候,如果隊列為空,線程將等待,新的數據加入后,將自動喚醒等待的線程;大部分的ExecutorService都是采用這種方式實現的
簡而言之,我們應該,優先使用java.util.concurrent包中提供的更高級的語言來代替wait,notify.
如果非要用wait和notify,注意以下幾點:
- wait前的條件檢查,當條件成立時,就跳過等待,可以保證不會死鎖,
- wait后的檢查,條件不成立繼續等待,可以保證安全
- 通常情況下都應該使用notifyAll,雖然從優化角度看,這樣不好.
同步器是使一個線程能夠等待另一個線程的對象。
最常用的同步器是CountDownLatch和Semaphore,不常用的是Barrier 和Exchanger。
倒計數器 鎖存器是一次性障礙,允許一個或者多個線程等待一個或者多個其它線程來做某些事情。
CountDownLatch的唯一構造器帶一個int類型的參數,這個int參數是指允許所有在等待線程被處理之前,必須在鎖存器上調用countDown方法的次數。
對于間歇式定時,應該始終使用System.nanoTime而不是System.cucurrentTimeMills。
應該始終使用wait循環模式來調用wait方法.不要在循環外調用wait方法.
小結?
直接使用 wait和notify,就像 用并發匯編語言進行編程一樣.而concurrent則提供了更高級的語言。?
沒有理由在新代碼中使用 wait和notify ,即使有,也很少。?
如果正在維護使用 wait和notify的代碼,則盡量在 while循環內部調用wait。?
應該優先使用notifyAll,而不是notify.
第70條:線程安全性的文檔化
如果你沒有在一個類的文檔中描述其行為的并發情況,使用這個類的程序員將不得不做出某些假設。如果這些假設是錯誤的,這樣得到的程序就可能缺少足夠的同步,或者過度同步。無論屬于哪種情況,都可能會發生嚴重的錯誤。
一個類為了可被多個線程安全使用,必須在文檔中清楚地說明它所支持的線程安全性級別。
- 不可變的——這個類的實例是不可變的。這樣的例子包括String,Long,BigInteger。
- 無條件的線程安全——這個類的實例是可變的,但是這個類有足夠的內部同步。例子包括Random,ConconcurrentHashMap。
- 有條件的線程安全——除了有些方法為進行安全的并發使用而需要外部同步之外,這種線程安全級別與無條件安全相同。例子包括:Collections.synhronized包裝返回的集合,它們的迭代器要求外部同步。
- 非線程安全——這個類的實例是可變的。為了并發使用它們,客戶必須利用自己選擇的外部同步包圍每個方法調用。例子包括ArrayList
- 線程對立的——這個類不能安全地被多個線程并發使用,即使所有的方法調用都被外圍同步包圍。
類的線程安全說明通常放在它的文檔中,但帶有特殊線程安全屬性的方法則應該在它們自己的文檔注釋中說明它們的屬性。
私有鎖只能用在無條件的線程安全類上。私有鎖對象模式特別適用于那些專門為繼承而設計的類。如果這種類適用它的實例作為鎖的對象,子類可能很容易在無意中妨礙基類的操作,反之亦然。
第71條:慎用延遲初始化
延遲初始化是延遲到需要域的值時才將它初始化的這種行為。
對于延遲初始化,最好建議“除非絕對必要,否則就不要那么做”。延遲化降低了初始化類或者創建實例的開銷,卻增加了訪問被延遲初始化的域的開銷。
如果域只是在類的實例部分被訪問,并且初始化這個域的開銷很高,可能就值得進行延遲初始化。
如果出于性能的考慮而需要對靜態域使用延遲初始化,就使用lazy initialization holder class 模式。保證在被用時初始化。
private static class FieldHolder { static final FieldType field = computeFieldValue(); } public static FieldType getField() { return FieldHolder.field; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果出于性能的考慮而需要對實例域使用延遲初始化,就使用雙重檢查模式。這種模式避免了在域被初始化之后訪問這個域時的鎖定開銷。
private volatile FieldType field; public FieldType getField() { FieldType result = field; if (result == null) { synchronized (this) { result = field; if (result == null) { field = result = computeFieldValue(); } } } return result; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
第一次檢查時沒有鎖定,看看這個域是否被初始化;第二次檢查時又鎖定。只有當第二次檢查時標明這個域沒有被初始化,才進行初始化。如果域已經被初始化就不會有鎖定,域被聲明為volatile很重要。
result局部變量的使用,是為了保證在已經被初始化的情況下,原來的變量只被讀取一次到局部變量result中,否則在比較的時候需要讀取一次,返回的時候還需要讀取一次。雖然這不是嚴格要求,但是可以提升性能。
簡而言之,大多數的域應該正常的進行初始化,否則,可以參考上面的規則,進行延遲初始化
第72條:不要依賴于線程調度器
- 當有多個線程可以運行時,由線程調度器決定哪些線程將會執行.以及運行多長時間。
- 任何依賴于線程調度器來達到正確性或者性能要求的程序,很有可能都是不可移植的。
- 要確保可運行線程的平均數量不明顯多于處理器的數量。
- 要編寫健壯,響應良好的,可移植的多線程應用程序,最好的辦法是確保可運行線程的平均數量不明顯多于處理器的數量。
- 線程優先級是Java平臺中移植性最差的部分,所以也不要用。
對于大多數程序員來說,Thread.yield的唯一用途,就是在測試期間人為的增加程序的并發性。?
在Java語言規范中,Thread.yield根本不做實質性工作,只是將控制權返回給它的調用者。
小結?
不要讓應用程序的并發性依賴于線程調度器?
不要依賴Thread.yield和線程優先級
第73條:避免使用線程組
線程組并沒有提供太多有用的功能,而且他們提供的許多功能還都有缺陷的。
如果你正在設計的一個類需要處理線程的邏輯組,或許就應該使用線程池executor。
《Effective Java中文版 第2版》PDF版下載:?
http://download.csdn.net/detail/xunzaosiyecao/9745699
作者:jiankunking 出處:http://blog.csdn.net/jiankunking
from:?http://blog.csdn.net/jiankunking/article/details/54834743
總結
以上是生活随笔為你收集整理的Effective Java读书笔记一:并发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IDEA代码生成插件CodeMaker
- 下一篇: java美元兑换,(Java实现) 美元