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