以前整理了一些關於Java虛擬機的內容,而做爲Java程序員,併發編程也是很重要的方面,下面就根據《Java併發編程實戰》這本書作一些整理。程序員
線程安全性:當多個線程訪問某個類時,這個類始終都能表現出正確的行爲,那麼就稱這個類是線程安全的。編程
咱們不只但願防止某個線程正在使用對象狀態而另外一個線程在同時修改該狀態;而且但願確保當一個線程修改了對象狀態後,其餘線程可以看到發生的狀態變化。數組
加鎖與可見性:加鎖的含義不只僅是侷限於互斥行爲,還包括內存可見性。緩存
用來確保將變量的更新操做通知到其餘線程。編譯器和運行時,會注意到這個變量是共享的,所以不會將該變量上的操做與其餘內存操做一塊兒重排序。不會執行加鎖操做,是一種比synchronized關鍵字更輕量級的同步機制。可是它不提供原子性。安全
當下面全部條件知足時,才應該使用volatile變量:併發
1.對變量的寫入操做不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。框架
2.該變量不會與其餘狀態變量一塊兒歸入不變性條件中異步
3.在訪問變量時不須要加鎖工具
一種避免同步的方式就是不共享數據。若是僅在單線程內訪問數據,就不須要同步,這種技術被稱爲線程封閉。在Swing以及JDBC connection中大量使用了線程封閉技術。性能
棧封閉:只能經過局部變量才能訪問對象。而局部變量位於執行線程的棧中。缺點:維護困難,容易使對象逸出。
TheadLocal類:它使線程中的某個值與保存值的對象關聯起來。當某個頻繁執行的操做須要一個臨時對象,而同時但願避免在每次執行時都從新分配該臨時對象,就可使用這項技術。這些特定於線程的值保存在Thread對象中,當線程終止後,這些值會做爲垃圾回收。Threadlocal變量相似於全局變量,它能下降代碼的可重用性,並在類之間引入隱含的耦合性,所以在使用時要格外當心。
不可變對象必定是線程安全的。
當知足如下條件時,對象纔是不可變的:
1.對象建立後,其狀態就不能修改
2.對象的全部域都是final類型
3.對象時正確建立的(在對象的建立期間,this引用沒有逸出)
好比有Vector和Hashtable。它們實現線程安全的方式是:把它們的狀態封裝起來,並對每一個公有方法都進行同步,使得每次只有一個線程能訪問容器的狀態。
及時失敗策略(fail-fast):當它們發現容器在迭代過程當中被修改時,就會拋出一個ConcurrentModificationException
加鎖是一種方案,但開銷大;克隆也是一種方案,但容器過大時,開銷也不小。
加鎖有時也避免不了隱藏迭代器的狀況。
Java5提供了多種併發容器來改進同步容器的性能。
採用了分段鎖機制。包含16個鎖的數組,每一個鎖保護散列桶的1/16.
返回的迭代器具備弱一致性,這意味着它能夠容忍併發的修改,當建立迭代器時會遍歷已有的元素,並能夠(可是不保證)在迭代器被構造後將修改操做反映給容器。
爲了反映容器的併發特性,size和isEmpty這些方法的語言被略微減弱了,由於它們返回的只是一個估計值,結果有可能已通過期了。但因爲返回值一直在變化,這些方法在併發環境下用處很小。咱們會更關注get、put、containKey和remove等操做。
劣勢:得到全部鎖的開銷更大,好比resize的時候
要確保鎖上的競爭頻率高於在鎖保護的數據上發生競爭的頻率。當每一個操做都請求多個變量時,鎖的粒度將很難下降。性能與可伸縮性的權衡。
每一個分段都維護一個獨立的計數器,調用size,返回值緩存到一個volatile變量,容器被修改則爲-1,正值返回,負值須要從新計算。
用於替代同步List
BlockingQueue
能夠延遲線程的進度直到其到達終止狀態。
CountDownLatch,初始化爲一個正數,表示須要等待的事情數量。countDown方法遞減計數器,表示有一個事情已經發生了,而await方法等待計數器達到零。
FutureTask能夠作閉鎖,它在Executor框架中表示異步任務,還能夠用來表示一些時間較長的計算。
信號量:Counting Semaphore用來控制同時訪問某個特定資源的操做數量。經過acquire和release來獲取和釋放信號量。
柵欄:與閉鎖的區別在於,全部線程必須同時到達柵欄位置,才能繼續執行。閉鎖用於等待時間,而柵欄用於等待其餘線程。await()會阻塞到全部線程都到達柵欄的位置。若是柵欄打開,則全部線程被釋放;而且每一個線程會拿到await返回的一個惟一的到達索引號,能夠利用索引號選出一個領導線程,並在下一次迭代中由改領導線程執行一些特殊的工做。
無限制建立線程的不足
1.線程生命週期的開銷很是高
2.資源消耗:過多的線程,會產生大量空閒線程佔用內存,給垃圾回收帶來壓力;線程競爭CPU資源也會帶來其餘的性能開銷。
3.穩定性:實際上,可建立線程的數量上存在一個限制。
它爲異步任務執行框架提供了基礎,該框架能支持多種不一樣類型的任務執行策略;另外,它的實現還提供了對生命週期的支持,以及統計信息收集、應用程序管理機制和性能監視等機制。
基於生產者-消費者模式,提交任務的操做至關於生產者(生成待完成的工做單元),執行任務的線程則至關於消費者(執行完這些工做單元)。將任務提交和任務執行解藕了。
好處:
1.分攤在線程建立和銷燬過程當中產生的巨大開銷
2.當請求到達時,工做線程一般已經存在,所以不會有延遲,提升了響應性
3.保持處理器合理忙碌,避免線程過分競爭資源
經過Executors中的靜態工廠方法來建立線程池:
1.newFixedThreadPool(int num):建立固定長度的線程池,滿了之後規模再也不變化。
2.newCachedThreadPool:建立一個可緩存的線程池,適合那些短任務;若是已有線程都在忙,會增長新線程;超過60s未工做的線程會被回收。
3.newSingleThreadExecutor:單線程,確保任務的順序(FIFO、LIFO等)。
4.newScheduledThreadPool(int num):建立一個固定長度的線程池,並且是以延遲或定時的方式來執行任務。
ExecutorService擴展了Executor接口,添加了一些用於生命週期管理的方法,同時還有一些用於任務提交的便利方法。
生命週期的三種狀態:運行,關閉,已終止。
shutdown方法採起平緩關閉:再也不接受新任務,等待已提交任務完成(包括還未開始執行的任務)。
shutdownNow方法採起粗暴的關閉:嘗試取消全部運行的任務,再也不啓動隊列中的未執行的任務。
Callable被認爲是主入口點將返回一個值,並可能拋出一個異常。描述的是一種抽象的計算任務。
Future表示一個任務的生命週期,並提供響應的方法來判斷是否已經完成或取消,以及獲取任務的結果和取消任務等。
ExecutorService中全部submit方法都將返回一個Future。將Runnable或Callable提交給Executor,並獲得一個Future用來得到任務的執行結果或取消任務。
還能夠顯示地爲某個指定地Runnable或Callable實例化一個FutureTask。
一般,中斷是實現取消的最合理方式。
經過Future來實現取消。
線程池的理想大小取決於被提交任務的類型以及所部署系統的特性。
若是線程池過大,那麼大量的線程將在相對不多的CPU和內存資源上發生競爭。
若是線程池太小,那麼不少處理器沒有被利用起來,從而下降了吞吐率。
用來實現Executors裏的一些工廠方法:
public ThreadPoolExecutor( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockinQueue<Ruannable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
線程池的基本大小、最大大小和存活時間等因素共同負責線程的建立與銷燬。
基本大小就是線程池的目標大小,即在沒有任務執行時線程池的大小,只有工做隊列滿了纔會建立超出這個數量的線程。
線程池的最大大小表示可同時活動的線程數量的上限。
若是某個線程的空閒時間超過了存活時間,那麼將被標記爲可回收的,而且當線程池的當前大小超過了基本大小時,這個線程將被終止。
ThreadPoolExecutor容許提供一個BlockingQueue來保存等待執行的任務。基本的任務排隊方法有3種:無界隊列、有界隊列和同步移交。
newFixedThreadPool和newSingleThreadExecutor在默認狀況下將使用一個無界的LinkedBlockingQueue。線程都忙碌,在隊列中等候,若是任務持續到達,並超過了線程池處理它們的速度,那麼隊列將無限制地增長。
一種更穩妥的資源管理策略是使用有界隊列,例如ArrayBlockingQueue、有界的LinkedBlockingQueue、PriorityBlockingQueue。有界隊列有助於避免資源耗盡的狀況發生。須要隊列的大小和線程池大小配合。
對於很是大的或者無界的線程池,能夠經過SynchronousQueue來避免任務排隊。它不是一個真正的隊列,而是一種在線程之間進行移交的機制。要將一個元素放入SynchronousQueue中,必須有另外一個線程正在等待接受這個元素。若是沒有線程正在等待,而且線程池的當前大小小於最大值,那麼將建立一個新的線程。不然根據飽和策略,這個任務將被拒絕。newCachedThreadPool就使用了這種機制。
只有當任務相互獨立時,爲線程池或工做隊列設置界限纔是合理的。
飽和策略
當有界隊列被填滿後,飽和策略開始發揮做用。能夠經過setRejectedExecutionHandler來修改。不一樣的飽和策略:AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy。
AbortPolicy:默認的策略,拋出異常,本身寫代碼來處理
DiscardPolicy:會悄悄拋棄該任務
DiscardOldestPolicy:拋棄下一個將被執行的任務,而後嘗試從新提交新任務。不要與優先隊列一塊兒使用。
CallerRunsPolicy:將某些任務回退到調用者,從而下降新任務的流量。它不會在線程池的某個線程中執行新提交的任務,而是在一個調用了execute的線程中執行該任務。
線程工廠
每當線程池須要建立一個線程時,都是經過線程工廠方法來完成的。ThreadFactory中只定義了一個方法newThread,每當線程池須要建立一個新線程時都會調用這個方法。許多狀況下都須要使用定製的線程工廠方法。
在子類中改寫方法beforeExecute,afterExecute和terminated。
若是每次至多隻能得到一個鎖,那麼就不會產生鎖順序死鎖。
顯示使用Lock類中的定時tryLock功能。內置鎖,沒有得到鎖會一直等下去。
可伸縮性指的是增長資源時,程序的吞吐量或處理能力相應地增長。
Amdahl定律描述:在增長計算資源地狀況下,程序在理論上可以實現最高加速比,這個值取決於程序中可並行組件與串行組件所佔比重。
1.減小鎖地持有時間
2.減小鎖地粒度
3.下降鎖地請求頻率
4.使用帶有協調機制的獨佔鎖,這些機制容許更高的並行性。
可重入的加鎖語義,靈活性。
輪詢鎖與定時鎖:tryLock方法。
輪詢鎖,若是不能得到所須要的鎖,那麼可使用可輪詢的鎖獲取方式,從而嘗試得到控制權。它會釋放已經得到的鎖,而後從新嘗試獲取全部鎖。
定時鎖,若是操做不能在指定的時間內給出結果,那麼就是使程序提早結束。
可中斷的鎖獲取操做。
公平性。
顯示的Condition對象。