【轉】Java併發編程注意事項

  1. 保證線程安全的三種方法:
    1. 不要跨線程訪問共享變量
    2. 使共享變量是final類型的
    3. 將共享變量的操做加上同步
  2. 一開始就將類設計成線程安全的, 比在後期從新修復它,更容易.
  3. 編寫多線程程序, 首先保證它是正確的, 其次再考慮性能.
  4. 無狀態或只讀對象永遠是線程安全的.
  5. 不要將一個共享變量裸露在多線程環境下(無同步或不可變性保護)
  6. 多線程環境下的延遲加載須要同步的保護, 由於延遲加載會形成對象重複實例化
  7. 對於volatile聲明的數值類型變量進行運算, 每每是不安全的(volatile只能保證可見性,不能保證原子性).詳見volatile原理與技巧中, 髒數據問題討論.
  8. 當一個線程請求得到它本身佔有的鎖時(同一把鎖的嵌套使用), 咱們稱該鎖爲可重入鎖.在jdk1.5併發包中, 提供了可重入鎖的java實現-ReentrantLock.
  9. 每一個共享變量,都應該由一個惟一肯定的鎖保護.建立與變量相同數目的ReentrantLock, 使他們負責每一個變量的線程安全.
  10. 雖然縮小同步塊的範圍, 能夠提高系統性能.但在保證原子性的狀況下, 不可將原子操做分解成多個synchronized塊.
  11. 在沒有同步的狀況下, 編譯器與處理器運行時的指令執行順序可能徹底出乎意料.緣由是, 編譯器或處理器爲了優化自身執行效率, 而對指令進行了的重排序(reordering).
  12. 當一個線程在沒有同步的狀況下讀取變量, 它可能會獲得一個過時值, 可是至少它能夠看到那個線程在當時設定的一個真實數值. 而不是憑空而來的值. 這種安全保證, 稱之爲最低限的安全性(out-of-thin-air safety)

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

  13. volatile變量,只能保證可見性, 沒法保證原子性.
  14. 某些耗時較長的網絡操做或IO, 確保執行時, 不要佔有鎖.
  15. 發佈(publish)對象, 指的是使它可以被當前範圍以外的代碼所使用.(引用傳遞)對象逸出(escape), 指的是一個對象在還沒有準備好時將它發佈.

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

this關鍵字引用對象逸出數組

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

  1. 必要時, 使用ThreadLocal變量確保線程封閉性(封閉線程每每是比較安全的, 但必定程度上會形成性能損耗)封閉對象的例子在實際使用過程當中, 比較常見, 例如 hibernate openSessionInView機制, jdbc的connection機制.
  2. 單一不可變對象每每是線程安全的(複雜不可變對象須要保證其內部成員變量也是不可變的)良好的多線程編程習慣是: 將全部的域都聲明爲final, 除非它們是可變的
  3. 保證共享變量的發佈是安全的a, 經過靜態初始化器初始化對象(jls 12.4.2敘述, jvm會保證靜態初始化變量是同步的) b, 將對象申明爲volatile或使用AtomicReference c, 保證對象是不可變的d, 將引用或可變操做都由鎖來保護
  4. 設計線程安全的類, 應該包括的基本要素: a, 肯定哪些是可變共享變量b, 肯定哪些是不可變的變量c, 指定一個管理併發訪問對象狀態的策略
  5. 將數據封裝在對象內部, 並保證對數據的訪問是原子的.建議採用volatile javabean模型或者構造同步的getter,setter.
  6. 線程限制性使構造線程安全的類變得更容易, 由於類的狀態被限制後, 分析它的線程安全性時, 就沒必要檢查完整的程序.
  7. 編寫併發程序, 須要更全的註釋, 更完整的文檔說明.
  8. 在須要細分鎖的分配時, 使用java監視器模式好於使用自身對象的監視器鎖.前者的靈活性更好.

Object target = new Object();網絡

// 這裏使用外部對象來做爲監視器, 而非this多線程

synchronized(target) {併發

// TODOjvm

}函數

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

  1. 設計併發程序時, 在保證伸縮性與性能折中的前提下, 優先考慮將共享變量委託給線程安全的類.由它來控制全局的併發訪問.
  2. 使用普通同步容器(Vector, Hashtable)的迭代器, 須要外部鎖來保證其原子性.緣由是, 普通同步容器產生的迭代器是非線程安全的.
  3. 在併發編程中, 須要容器支持的時候, 優先考慮使用jdk併發容器(ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList...).
  4. ConcurrentHashMap, CopyOnWriteArrayList併發容器的迭代器,以及全範圍的size(), isEmpty() 都表現出弱一致性.他們只能標示容器當時的一個數據狀態. 沒法完整響應容器以後的變化和修改.
  5. 使用有界隊列, 在隊列充滿或爲空時, 阻塞全部的讀與寫操做. (實現生產-消費的良好方案) BlockQueue下的實現有LinkedBlockingQueue與ArrayBlockingQueue, 前者爲鏈表, 可變操做頻繁優先考慮,後者爲數組, 讀取操做頻繁優先考慮. PriorityBlockingQueue是一個按優先級順序排列的阻塞隊列, 它能夠對全部置入的元素進行排序(實現Comparator接口)
  6. 當一個方法, 能拋出InterruptedException, 則意味着, 這個方法是一個可阻塞的方法, 若是它被中斷, 將提早結束阻塞狀態.當你調用一個阻塞方法, 也就意味着, 自己也稱爲了一個阻塞方法, 由於你必須等待阻塞方法返回.

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

相關文章
相關標籤/搜索