Java并發編程注意事項
-
保證線程安全的三種方法:
-
不要跨線程訪問共享變量
-
使共享變量是final類型的
-
將共享變量的操作加上同步
-
-
一開始就將類設計成線程安全的, 比在后期重新修復它,更容易.
-
編寫多線程程序, 首先保證它是正確的, 其次再考慮性能.
-
無狀態或只讀對象永遠是線程安全的.
-
不要將一個共享變量裸露在多線程環境下(無同步或不可變性保護)
-
多線程環境下的延遲加載需要同步的保護, 因為延遲加載會造成對象重復實例化
-
對于volatile聲明的數值類型變量進行運算, 往往是不安全的(volatile只能保證可見性,不能保證原子性).詳見volatile原理與技巧中, 臟數據問題討論.
-
當一個線程請求獲得它自己占有的鎖時(同一把鎖的嵌套使用), 我們稱該鎖為可重入鎖.在jdk1.5并發包中, 提供了可重入鎖的java實現-ReentrantLock.
-
每個共享變量,都應該由一個唯一確定的鎖保護.創建與變量相同數目的ReentrantLock, 使他們負責每個變量的線程安全.
-
雖然縮小同步塊的范圍, 可以提升系統性能.但在保證原子性的情況下, 不可將原子操作分解成多個synchronized塊.
-
在沒有同步的情況下, 編譯器與處理器運行時的指令執行順序可能完全出乎意料.原因是, 編譯器或處理器為了優化自身執行效率, 而對指令進行了的重排序(reordering).
-
當一個線程在沒有同步的情況下讀取變量, 它可能會得到一個過期值, 但是至少它可以看到那個線程在當時設定的一個真實數值. 而不是憑空而來的值. 這種安全保證, 稱之為最低限的安全性(out-of-thin-air safety)
在開發并發應用程序時, 有時為了大幅度提高系統的吞吐量與性能, 會采用這種無保障的做法.但是針對, 數值的運算, 仍舊是被否決的.
-
volatile變量,只能保證可見性, 無法保證原子性.
-
某些耗時較長的網絡操作或IO, 確保執行時, 不要占有鎖.
-
發布(publish)對象, 指的是使它能夠被當前范圍之外的代碼所使用.(引用傳遞)對象逸出(escape), 指的是一個對象在尚未準備好時將它發布.
原則: 為防止逸出, 對象必須要被完全構造完后, 才可以被發布(最好的解決方式是采用同步)
this關鍵字引用對象逸出
例子: 在構造函數中, 開啟線程, 并將自身對象this傳入線程, 造成引用傳遞.而此時, 構造函數尚未執行完, 就會發生對象逸出了.
-
必要時, 使用ThreadLocal變量確保線程封閉性(封閉線程往往是比較安全的, 但一定程度上會造成性能損耗)封閉對象的例子在實際使用過程中, 比較常見, 例如 hibernate openSessionInView機制, jdbc的connection機制.
-
單一不可變對象往往是線程安全的(復雜不可變對象需要保證其內部成員變量也是不可變的)良好的多線程編程習慣是: 將所有的域都聲明為final, 除非它們是可變的
-
保證共享變量的發布是安全的a, 通過靜態初始化器初始化對象(jls 12.4.2敘述, jvm會保證靜態初始化變量是同步的) b, 將對象申明為volatile或使用AtomicReference c, 保證對象是不可變的d, 將引用或可變操作都由鎖來保護
-
設計線程安全的類, 應該包括的基本要素: a, 確定哪些是可變共享變量b, 確定哪些是不可變的變量c, 指定一個管理并發訪問對象狀態的策略
-
將數據封裝在對象內部, 并保證對數據的訪問是原子的.建議采用volatile javabean模型或者構造同步的getter,setter.
-
線程限制性使構造線程安全的類變得更容易, 因為類的狀態被限制后, 分析它的線程安全性時, 就不必檢查完整的程序.
-
編寫并發程序, 需要更全的注釋, 更完整的文檔說明.
-
在需要細分鎖的分配時, 使用java監視器模式好于使用自身對象的監視器鎖.前者的靈活性更好.
Object target = new Object();
// 這里使用外部對象來作為監視器, 而非this
synchronized(target) {
// TODO
}
針對java monitor pattern, 實際上ReentrantLock的實現更易于并發編程.功能上, 也更強大.
-
設計并發程序時, 在保證伸縮性與性能折中的前提下, 優先考慮將共享變量委托給線程安全的類.由它來控制全局的并發訪問.
-
使用普通同步容器(Vector, Hashtable)的迭代器, 需要外部鎖來保證其原子性.原因是, 普通同步容器產生的迭代器是非線程安全的.
-
在并發編程中, 需要容器支持的時候, 優先考慮使用jdk并發容器(ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList...).
-
ConcurrentHashMap, CopyOnWriteArrayList并發容器的迭代器,以及全范圍的size(), isEmpty() 都表現出弱一致性.他們只能標示容器當時的一個數據狀態. 無法完整響應容器之后的變化和修改.
-
使用有界隊列, 在隊列充滿或為空時, 阻塞所有的讀與寫操作. (實現生產-消費的良好方案) BlockQueue下的實現有LinkedBlockingQueue與ArrayBlockingQueue, 前者為鏈表, 可變操作頻繁優先考慮,后者為數組, 讀取操作頻繁優先考慮. PriorityBlockingQueue是一個按優先級順序排列的阻塞隊列, 它可以對所有置入的元素進行排序(實現Comparator接口)
-
當一個方法, 能拋出InterruptedException, 則意味著, 這個方法是一個可阻塞的方法, 如果它被中斷, 將提前結束阻塞狀態.當你調用一個阻塞方法, 也就意味著, 本身也稱為了一個阻塞方法, 因為你必須等待阻塞方法返回.
如果阻塞方法拋出了中斷異常, 我們需要做的是, 將其往上層拋, 除非當前已經是需要捕獲異常的層次.如果當前方法, 不能拋出InterruptedException, 可以使用Thread.currentThread.interrupt()方法, 手動進行中斷.
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!