這學期開始我準備重新夯實一下Java基礎,那么首當其沖的就是并發編程和Java虛擬機了,于是我在一個月的時間里終于把《Java并發編程實踐總結》啃下來了,真的是啃,因為這本書對于沒有多少并發編程經驗的我來說實在晦澀難懂,以前做Android的時候網絡編程還略微用過一點,現在學Java Web,Spring和Servlet都已經把多線程封裝好了,沒有什么機會用到,但是了解多線程的設計思想及其底層設計還是很重要的。
競態條件
當某個計算的正確性取決于多個線程的交替執行時序時,就會發生競態條件 。
競態條件是并發編程出現問題的一大主因,“先檢查后執行”操作就是競態條件的最常見的類型,就是根據一個可能失效的結果來決定怎么進行下一步計算。
//競態條件偽代碼
if(一個可能為真可能為假的條件)
操作A
在多線程環境下,在你確定條件為真并在操作A執行前,另一個線程又將條件置為假,這將此代碼置于一種不穩定的狀態,即有可能是對的也有可能是錯的,全靠運氣。
可重入
如果某個線程試圖獲得一個已經由它自己持有的鎖,如果成功,則這個鎖是可重入的,否則是不可重入的
重入鎖的設計思想是每個鎖關聯一個計數值和一個線程。計數值為0,則沒有任何線程持有該鎖。若線程試圖獲取該鎖而該鎖計數值不為0時,則判斷該線程是否是已經持有本鎖的線程,如果是則獲取成功,計數值+1。否則獲取失敗。線程每一次退出同步區時,計數值-1,當計數值為0時,該鎖被釋放。
內置鎖不是可重入的,ReentrantLock則可以重入。
重排序
在編譯器中生成的指令順序,可以與源代碼中的順序不同
第一次看到重排序時,我就在想JVM這不是在亂搞嗎,我辛辛苦苦寫的代碼,你還來給我重排序。后來了解到重排序提升了編譯效率,但是重排序機制也不是對所有代碼都重排序,操作A和操作B之間如果滿足了偏序關系,即“Happens-Before”關系,那么JVM就不會對它們的代碼進行重排序。
Volatile關鍵字
把變量聲明為Volatile后,編譯器會注意到這個變量是共享的,因此不會把該變量上的操作與其他內存操作一起重排序,例如在多線程中需要設置一個變量為循環條件時,最好將其設為volatile
volatile boolean asleep;
...
while(!asleep)
{
.....
}
同步工具類
a.CountDownLatch
CountDownLatch稱之為閉鎖,可以使一個或多個線程等待一組事件的發生。CountDownLatch主要有兩個方法,countDown()和await(),沒調用一次countdown則計數值減一,await將一直阻塞直到計數值為0。
b.FutureTask
FutureTask表示的計算是通過Callable來實現的,相當于一種可生成結果的Runnable,并且可以處于以下三種狀態:等待運行,正在運行,運行完成。FutureTask.get方法調用后如果該任務已經完成,則立即返回結果,否則一直阻塞到任務完成或者拋出異常。
無限制創建線程的不足
- 線程生命周期的開銷非常高。
- 資源消耗
- 穩定性因素,可能拋出OutOfMemoryError錯誤。
Executor
由于無限制創建線程并不是一種好的策略,于是我們可以使用有界隊列來防止這一情況的產生,Executor就應運而生了。
Executor基于生產者消費者模式,如果要在程序中實現一個生產者消費者模式,最簡單的方法就是使用Executor。
線程池
線程池可以復用多個線程從而分攤線程創建和銷毀過程中的巨大開銷,由于任務到來時線程已經存在了從而還提高了響應性,可以調用Executors中的幾個靜態方法來創建一個線程池。
-
newCachedThreadPool()
線程池的規模無限制,線程超過需求就創建,小于需求就回收
-
newFixedThreadPool(int)
線程池的規模是固定的
-
newScheduledThreadPool(int)
固定長度的線程池,以延時或定時的方式執行任務
-
SingleThreadExecutor()
只包含一個線程的線程池
飽和策略
前面說了線程池使用有界隊列來管理線程,如果飽和了怎么辦呢?就需要用到提前定義的飽和策略:
- AbortPolicy: 直接拋出異常,由調用者自己捕獲編寫處理代碼。
- DiscardPolicy: 不處理該任務,直接丟棄
- DiscardOldestPolicy: 丟棄隊列里最近的一個任務,并執行當前任務。
- CallerRunsPolicy: 只用調用者所在線程來運行任務。
顯式鎖
Lock和ReentrantLock
Lock和ReentrantLock與內置鎖的區別是可以中斷一個正在等待獲取鎖的線程,或者使該線程無限地等待下去,Lock和ReentrantLock必須在finally代碼塊中釋放鎖,否則一直持有鎖就極易形成死鎖
讀寫鎖
讀寫鎖其實是由兩個Lock組成的
public interface ReadWriteLock
{
Lock readLock();
Lock writeLock();
}
在讀寫鎖的加鎖策略中,允許多個讀操作同時進行,但每次只允許一個寫操作。
以上就是我讀第一遍的部分收獲,此書太過經典,第一遍我大概只掌握了40%的知識,等以后有時間了再二刷。