多线程速成
##多線程速成要點
? 多線程協作時,因為對資源的鎖定與等待會產生死鎖,需要了解產生死鎖的四個基本條件,要明白競爭條件與臨界區的概念,知道通過破壞造成死鎖的4個條件來防止死鎖。
除了了解進程間的通信方式,還要知道線程的通信方式,通信主要指線程之間的協作機制,例如Wait、Notify
另外需要知道Java為多線程提供的一些機制,例如Threadlocal用來保存線程獨享的數據,Fork/Foin機制用于大任務的分割與匯總,Volatile對多線程數據可見性的保證以及線程的中斷機制。
其他還有: Threadlocal的實現機制。Fork/Join的工作竊取算法等內容。
1、線程的狀態轉換
線程是Jvm執行任務的最小單元,理解線程的狀態轉換是理解后續多線程問題的基礎。在Jvm運行中,線程一共有New、Runnable、Blocked、Waiting、Timed_waiting、Terminated六種狀態,這些狀態對應Thread.State枚舉類中的狀態。當創建一個線程的時候,線程處在New狀態,運行Thread的Start方法后,線程進入Runnable可運行狀態。這個時候,所有可運行狀態的線程并不能馬上運行,而是需要先進入就緒狀態等待線程調度,如圖中間的Ready狀態。在獲取到Cpu后才能進入運行狀態,如圖中的Running。運行狀態可以隨著不同條件轉換成除New以外的其他狀態。先看左邊,在運行態中的線程進入Synchronized同步塊或者同步方法時,如果獲取鎖失敗,則會進入到Blocked狀態。當獲取到鎖后,會從Blocked狀態恢復到就緒狀態。再看右邊,運行中的線程還會進入等待狀態,這兩個等待一個是有超時時間的等待,例如調用Object.wait、Thread.join等。另外一個時無超時的等待,例如調用Thread.join或者Locksupport.park。這兩種等待都可以通過Notify或Unpark結束等待狀態恢復到就緒狀態。最后是線程運行完成結束時,如圖下方,線程狀態變成Terminated2、CAS與ABA問題
解決線程同步與互斥的主要方式是Cas、Synchronized、和Lock。Cas是屬于樂觀鎖的一種實現,是一種輕量級鎖,Juc中很多工具類的實現就是基于Cas。Cas操作是線程在讀取數據時不進行加鎖,在準備寫回數據時,比較原值是否修改,若未被其他線程修改則寫回,若已被修改,則重新執行讀取流程。這是一種樂觀策略,認為并發操作并不總會發生。比較并寫回的操作是通過操作系統原語實現的,保證執行過程中不會被中斷。Cas容易出現Aba問題,如果線程T1讀取值A之后,發生過兩次寫入,先由線程T2寫回了b,又由T3寫回了A,此時T1在寫回比較時,值還是A,就無法判斷是否發生過修改。Aba問題不一定會影響結果,但還是需要防范,解決的辦法可以增加額外的標志位或者時間戳。Juc工具包中提供了這樣的類。3、Synchronized
Synchronized是最常用的線程同步手段之一,它是如何保證同一時刻只有一個線程可以進入臨界區呢?我們知道Synchronized是對對象進行加鎖,在Jvm中,對象在內存中分為三塊區域:對象頭、實例數據和對齊填充。在對象頭中保存了鎖標志位和指向Monitor對象的起始地址。當Monitor被某個線程持有后,就會處于鎖定狀態,Owner部分會指向持有Monitor對象的線程。另外Monitor中還有兩個隊列,用來存放進入及等待獲取鎖的線程。Synchronized應用在方法上時,在字節碼中是通過方法的AccCC_Synchronized標志來實現的,Synchronized應用在同步塊上時,在字節碼中是通過Monitorenter和Monitorexit實現的。針對Synchronized獲取鎖的方式,Jvm使用了鎖升級的優化方式,就是先使用偏向鎖優先同一線程再次獲取鎖,如果失敗,就升級為Cas輕量級鎖,如果再失敗會短暫自旋,防止線程被系統掛起。最后如果以上都失敗就是升級為重量級鎖。4、Aqs與Lock
在介紹Lock前,先介紹Aqs,也就是隊列同步器,這是實現Lock的基礎。Aqs有一個State標記位,值為1時表示有線程占用,其他線程需要進入到同步隊列等待。同步隊列是一個雙向鏈表。當獲得鎖的線程需要等待某個條件時,會進入Condition的等待隊列,等待隊列可以有多個。當Condition條件滿足時,線程會從等待隊列重新進入到同步隊列進行獲取鎖的競爭。Reentrantlock就是基于Aqs實現的,Reentrantlock內部有公平鎖和非公平鎖兩種實現,差別就在于新來的線程會不會比已經在同步隊列中的等待線程更早獲得鎖。和Reentrantlock實現方式類似,Semaphore也是基于aqs,差別在于Reentrantlock是獨占鎖,Semaphore是共享鎖。5、線程池
線程池通過復用線程,避免線程頻繁創建和銷毀。Java的Executors工具類中,提供了5種類型線程池的創建方法,它們的特點和適用場景如下:第1種是:固定大小線程池,特點是線程數固定,使用無界隊列,適用于任務數量不均勻的場景、對內存壓力不敏感,但系統負載比較敏感的場景;第2種是:Cached線程池,特點是不限制線程數,適用于要求低延遲的短期任務場景;第3種是:單線程線程池,也就是一個線程的固定線程池,適用于需要異步執行但需要保證任務順序的場景;第4種是:Scheduled線程池,適用于定期執行任務場景,支持按固定頻率定期執行和按固定延時定期執行兩種方式;第5種是:工作竊取線程池,使用的ForkJoinPool,是固定并行度的多任務隊列,適合任務執行時長不均勻的場景。6、線程池參數介紹
前面提到的線程池,除了工作竊取線程池外,都是通過ThreadPoolExecutor的不同初始化參數來創建的。第1個參數:設置核心線程數。默認情況下核心線程會一直存活。第2個參數:設置最大線程數。決定線程池最多可以創建的多少線程。第3個參數和第4個參數:用來設置線程空閑時間,和空閑時間的單位,當線程閑置超過空閑時間就會被銷毀。可以通過AllowCoreThreadTimeOut方法來允許核心線程被回收。第5個參數:設置緩沖隊列,圖中左下方的三個隊列是設置線程池時常使用的緩沖隊列。其中Array Blocking Queue是一個有界隊列,就是指隊列有最大容量限制。Linked Blocking Queue是無界隊列,就是隊列不限制容量。最后一個是Synchronous Queue,是一個同步隊列,內部沒有緩沖區。第6個參數:設置線程池工廠方法,線程工廠用來創建新線程,可以用來對線程的一些屬性進行定制,例如線程的Group、線程名、優先級等。一般使用默認工廠類即可。第7個參數:設置線程池滿時的拒絕策略。如右下角所示有四種策略,abort策略在線程池滿后,提交新任務時會拋出Rejected Execution Exception,這個也是默認的拒絕策略。Discard策略會在提交失敗時對任務直接進行丟棄。CallerRuns策略會在提交失敗時,由提交任務的線程直接執行提交的任務。Discard Oldest策略會丟棄最早提交的任務。■前面的5種線程池都是使用怎樣的參數來創建的呢?固定大小線程池創建時核心和最大線程數都設置成指定的線程數,這樣線程池中就只會使用固定大小的線程數。隊列使用無界隊列Linked Blocking Queue。Single線程池就是線程數設置為1的固定線程池。Cached線程池的核心線程數設置為0,最大線程數是Integer.Max_Value,主要是通過把緩沖隊列設置成SynchronousQueue,這樣只要沒有空閑線程就會新建。scheduled線程池與前幾種不同的是使用了Delayed Work Queue,這是一種按延遲時間獲取任務的優先級隊列。總結
- 上一篇: jquery php抽奖转盘,JQuer
- 下一篇: 高德地图 爬坑之旅