Java併發編程實踐筆記

1, 保證線程安全的三種方法 : a, 不要跨線程訪問共享變量 b, 使共享變量是 final類型的 c, 將共享變量的操做加上同步java

2, 一開始就將類設計成線程安全的 , 比在後期從新修復它 ,更容易 .編程

3, 編寫多線程程序 , 首先保證它是正確的 , 其次再考慮性能 .數組

4, 無狀態或只讀對象永遠是線程安全的 .安全

5, 不要將一個共享變量裸露在多線程環境下 (無同步或不可變性保護 )網絡

6, 多線程環境下的延遲加載須要同步的保護 , 由於延遲加載會形成對象重複實例化多線程

7, 對於 volatile 聲明的數值類型變量進行運算 , 每每是不安全的 (volatile 只能保證可見性 , 不能保證原子性 ). 詳見 volatile 原理與技巧中 , 髒數據問題討論 . 8, 當一個線程請求得到它本身佔有的鎖時 ( 同一把鎖的嵌套使用 ), 咱們稱該鎖爲可重入鎖 . 在 jdk1.5 併發包中 , 提供了可重入鎖的 java 實現 -ReentrantLock.併發

9, 每一個共享變量 , 都應該由一個惟一肯定的鎖保護 . 建立與變量相同數目的 ReentrantLock, 使他們負責每一個變量的線程安全 .jvm

 

10,雖然縮小同步塊的範圍 , 能夠提高系統性能 . 但在保證原子性的狀況下 , 不可將原子操做分解成多個 synchronized塊 .函數

11, 在沒有同步的狀況下 , 編譯器與處理器運行時的指令執行順序可能徹底出乎意料 . 緣由是 , 編譯器或處理器爲了優化自身執行效率 , 而對指令進行了的重排序 (reordering).性能

12, 當一個線程在沒有同步的狀況下讀取變量 , 它可能會獲得一個過時值 , 可是至少它能夠看到那個 線程在當時設定的一個真實數值 . 而不是憑空而來的值 . 這種安全保證 , 稱之爲 最低限的安全性 (out-of-thin-air safety)

在開發併發應用程序時 , 有時爲了大幅度提升系統的吞吐量與性能 , 會採用這種無保障的作法 . 可是針對 , 數值的運算 , 仍舊是被否決的 . 13, volatile 變量 , 只能保證可見性 , 沒法保證原子性 .

14, 某些耗時較長的網絡操做或 IO, 確保執行時 , 不要佔有鎖 .

15, 發佈 (publish) 對象 , 指的是使它可以被當前範圍以外的代碼所使用 .( 引用傳遞 ) 對象逸出 (escape), 指的是一個對象在還沒有準備好時將它發佈 .

原則 : 爲防止逸出 , 對象必需要被徹底構造完後 , 才能夠被髮布 ( 最好的解決方式是採用同步 )

this 關鍵字引用對象逸出 例子 : 在構造函數中 , 開啓線程 , 並將自身對象 this 傳入線程 , 形成引用傳遞 . 而此時 , 構造函數還沒有執行完 , 就會發生對象逸出了 .

16, 必要時 , 使用 ThreadLocal變量確保線程封閉性 (封閉線程每每是比較安全的 , 但必定程度上會形成性能損耗 ) 封閉對象的例子在實際使用過程當中 , 比較常見 , 例如 hibernate openSessionInView機制 , jdbc的 connection機制 .

17, 單一不可變對象每每是線程安全的 (複雜不可變對象須要保證其內部成員變量也是不可變的 ) 良好的多線程編程習慣是 : 將全部的域都聲明爲 final, 除非它們是可變的

18, 保證共享變量的發佈是安全的 a, 經過靜態初始化器初始化對象 (jls 12.4.2 敘述 , jvm 會保證靜態初始化變量是同步的 ) b, 將對象申明爲 volatile 或使用 AtomicReference c, 保證對象是不可變的 d, 將引用或可變操做都由鎖來保護

19, 設計線程安全的類 , 應該包括的基本要素 : a, 肯定哪些是可變共享變量 b, 肯定哪些是不可變的變量 c, 指定一個管理併發訪問對象狀態的策略

20, 將數據封裝在對象內部 , 並保證對數據的訪問是原子的 . 建議採用 volatile javabean 模型或者構造同步的 getter,setter.

21, 線程限制性使構造線程安全的類變得更容易 , 由於類的狀態被限制後 , 分析它的線程安全性時 , 就沒必要檢查完整的程序 .

22, 編寫併發程序 , 須要更全的註釋 , 更完整的文檔說明 .

23, 在須要細分鎖的分配時 , 使用 java監視器模式好於使用自身對象的監視器鎖 . 前者的靈活性更好 .

Object target = new Object(); // 這裏使用外部對象來做爲監視器 , 而非 this synchronized(target) { // TODO }

針對 java monitor pattern, 實際上 ReentrantLock的實現更易於併發編程 . 功能上 , 也更強大 .

24, 設計併發程序時 , 在保證伸縮性與性能折中的前提下 , 優先考慮將共享變量委託給線程安全的類 . 由它來控制全局的併發訪問 .

25, 使用普通同步容器 (Vector, Hashtable) 的迭代器 , 須要外部鎖來保證其原子性 . 緣由是 , 普通同步容器產生的迭代器是非線程安全的 . 26, 在併發編程中 , 須要容器支持的時候 , 優先考慮使用 jdk 併發容器 (ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList…).

27, ConcurrentHashMap, CopyOnWriteArrayList 併發容器的迭代器 , 以及全範圍的 size(), isEmpty() 都表現出弱一致性 . 他們只能標示容器當時的一個數據狀態 . 沒法完整響應容器以後的變化和修改 .

28, 使用有界隊列 , 在隊列充滿或爲空時 , 阻塞全部的讀與寫操做 . ( 實現生產 – 消費的良好方案 ) BlockQueue 下的實現有 LinkedBlockingQueue 與 ArrayBlockingQueue, 前者爲鏈表 , 可變操做頻繁優先考慮 , 後者爲數組 , 讀取操做頻繁優先考慮 . PriorityBlockingQueue 是一個按優先級順序排列的阻塞隊列 , 它能夠對全部置入的元素進行排序 ( 實現 Comparator 接口 )

29, 當一個方法 , 能拋出 InterruptedException, 則意味着 , 這個方法是一個可阻塞的方法 , 若是它被中斷 , 將提早結束阻塞狀態 . 當你調用一個阻塞方法 , 也就意味着 , 自己也稱爲了一個阻塞方法 , 由於你必須等待阻塞方法返回 .

若是阻塞方法拋出了中斷異常 , 咱們須要作的是 , 將其往上層拋 , 除非當前已是須要捕獲異常的層次 . 若是當前方法 , 不能拋出 InterruptedException, 可使用 Thread.currentThread.interrupt() 方法 , 手動進行中斷 .

轉載請註明原文連接:http://kenwublog.com/java-concurrency-in-practise-note

相關文章
相關標籤/搜索