Java多線程并發(fā)

一、 java多線程創(chuàng)建方式
-
繼承Tread類
將自己的類繼承Tread類,并重寫run()方法。
Tread類的start()方法是一個native方法
-
實現Runable接口
將自己的類實現Runable接口,重寫run方法。
實例出一個task。再實例化一個Thread,并傳入task。
-
實現Callable接口(有返回值)
執(zhí)行callable task 可以返回一個Future 對象,通過get方法能獲得任務的返回值。
二、 4種線程池
-
newCachedTreadPool
適用于短期異步任務。
- 當任務申請線程,若有空閑線程,則重用這個空閑線程。
- 若無空閑線程,則創(chuàng)建新線程。
- 會終止并從緩存移出60秒未被使用的線程。
-
newFixedTreadPool
固定數量的線程池,用隊列排隊。
適用于子啊任意點大多數線程都處于活動狀態(tài)的程序。
有任務申請線程- 隊列滿,排隊等待,不滿,得到線程。
- 若某個線程異常終止 , 創(chuàng)建新線程繼續(xù)任務。
- 在線程被顯式關閉前,線程將一直存在于線程池。
-
newScheduledThreadPool
可設置內部線程延遲執(zhí)行或定期執(zhí)行。
-
newSingelThreadExcutor
線程池只有單個線程。
三、 線程生命周期

它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5 種狀態(tài)。
新建狀態(tài)。JVM分配內存,并初始化成員變量。
就緒狀態(tài)。調用start()方法后,處于就緒狀態(tài)。JVM創(chuàng)建方法棧和PC,等待調度運行。
運行狀態(tài)。得到CPU控制權,開始執(zhí)行run方法。
-
阻塞狀態(tài)
讓出CPU使用權
- 等待阻塞 object.wait。JVM將線程放入該對象的等待鎖池。
- 同步阻塞 lock 。同步鎖被別的線程占用,JVM將線程放入鎖池(look pool) ;
- 其他阻塞 sleep/join 或IO ,不釋放鎖,當超時,或IO結束,線程重新變成Runable。
四、 終止線程的4種方式
正常結束
使用退出標志
對于一些守護線程,當外部條件滿足時才能退出。這個時候可以用類似于While(!biaozhi) 循環(huán) 的方式控制線程是否終止。-
Interrupt()方法
該方法實質是設置中斷標志位為true ,程序員通過檢測中斷標志位來結束線程。線程處于阻塞狀態(tài): 如調用了sleep、wait等方法后線程阻塞。此時調用線程的interrupt方法會拋出InterruptException異常。通過捕獲這個異常然后break跳出循環(huán),終止線程。
-
未阻塞 :通過調用isInterrupted() 判斷線程中斷標志位來退出循環(huán)。
while (!isInterrupted()){to do}
注意 interrupted()方法返回狀態(tài)后,會重置狀態(tài)。
public class ThreadSafe extends Thread { public void run() { while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標志來退出 try{ Thread.sleep(5*1000);//阻塞過程捕獲中斷異常 來退出 }catch(InterruptedException e){ e.printStackTrace(); break;//捕獲到異常之后,執(zhí)行break 跳出循環(huán) } } } } -
使用stop方法.不安全
線程會突然釋放所有的鎖,可能導致數據的破壞。
五、 sleep 和 wait 的區(qū)別
sleep屬于 Thread 類 . wait 屬于Object類
sleep后會自動恢復運行,而wait需要notify
sleep不會釋放鎖,wait釋放鎖
六、 start和run的區(qū)別
start() 是線程啟動方法。run是線程業(yè)務代碼方法
start() 之后創(chuàng)建線程處于就緒狀態(tài),如果直接調用run()。會在主線程直接運行業(yè)務代碼。
七、 守護線程
為用戶線程提供服務的線程
設置方法setDaemon(true)
特性
守護線程可以不依賴于控制終端(應用)而依賴于系統。
在所有用戶線程結束之后再自動結束。
八、java鎖
-
樂觀鎖
讀數據的時候不上鎖, 寫數據的時候上鎖。寫入的時候先讀出版本號V1,然后進行CAS原子操作。比較V1和加鎖后的V2是否相同,若相同,更新;若不同,更新失敗。
悲觀鎖
讀寫數據的時候都加鎖。
比如: Synchronized 同步 和 RetreenLock 鎖-
自旋鎖
讓未獲得鎖的線程不掛起,仍然保持內核態(tài)。等待鎖的釋放。此時會消耗cpu。
適用于持有鎖的線程會在短時間釋放鎖的情況。
優(yōu)點:
減少線程阻塞,提高性能。自旋消耗小于線程阻塞掛起和幻想的消耗。
若持有鎖的線程長時間占有鎖。則自旋所需的cpu性能白白消耗,同時是的其他需要cpu的線程沒有獲得cpu造成浪費。
自旋鎖時間閾值
自旋周期jad1.5的時候固定的,jdk1.6的時候是自適應的。 -
Synchronized同步鎖
悲觀鎖。獨占式、可重入。
- 作用于方法, 鎖實例。當調用實例中被鎖方法,阻塞
- 作用于靜態(tài)方法, 鎖Class實例。Class相關數據只存在于方法區(qū)(元空間) ,即鎖住所有調用該方法的線程。
- 作用于對象時,當調用被鎖實例中任何方法,阻塞。
核心組件
Synchronized的組件1) Contention List:競爭隊列,所有請求鎖的線程首先被放在這個競爭隊列中; 2) Wait Set:哪些調用wait 方法被阻塞的線程被 放置在這里; 3) Entry List:Contention List 中那些有資格成為候選資源的線程被移動到Entry List 中; 4) OnDeck:任意時刻,最多只有一個線程正在競爭鎖資源,該線程被成為OnDeck; 5) Owner:當前已經獲取到所資源的線程被稱為Owner; 6) !Owner:當前釋放鎖的線程。Synchronized 實現
- 當 Owner 線程 unlock時, JVM將EntryList中的一個線程移入OnDeck 。同時,將ContentionList中的一些線程移入EnryList。
- onDeck中的線程有競爭鎖的權利,而不是直接得到鎖。它與處于自旋狀態(tài)的線程競爭鎖。
- 持有鎖的線程調用wait方法, 該線程釋放鎖,并進入Wait Set--等待池。當調用notify時,將Wait Set 中線程加入EnryList--有資格競爭鎖的等待隊列。
- Synchronized是非公平鎖,線程在進入ContentList之前,會先嘗試獲得自旋鎖,并可能直接與OnDeck中的線程產生競爭,搶占鎖資源。
- 加鎖實際上是在競爭monitor對象
-
ReentrantLock 可重入鎖
ReentrantLock 和 synchronized都是可重入鎖.
可重入鎖與不可重入鎖的區(qū)別不可重入鎖:只判斷這個鎖有沒有被鎖上,只要被鎖上申請鎖的線程都會被要求等待。實現簡單 可重入鎖:不僅判斷鎖有沒有被鎖上,還會判斷鎖是誰鎖上的,當就是自己鎖上的時候,那么他依舊可以再次訪問臨界資源,并把加鎖次數加一。 設計了加鎖次數,以在解鎖的時候,可以確保所有加鎖的過程都解鎖了,其他線程才能訪問。關鍵源碼 (上鎖):
while(isLocked && lockedBy != thread){ wait(); }判斷了是否上鎖,并且請求鎖的線程是否是當前線程。ReentrantLock 與 synchronized相比:
- 優(yōu)勢:可中斷、公平鎖、多個鎖。
- 使用lock 和 unlock 加鎖解鎖。解鎖必須在finally控制塊中完成。
Condition 類和Object類鎖的區(qū)別
- Condition.await 和Object.wait等效
- signal 和 notify 等效
- ReentrantLock可以喚醒指定條件線程。
Lock 和 tryLock
- tryLock 沒獲得鎖返回false。
- lock 沒獲得鎖阻塞 ,等待獲得鎖.
Lock 和 lockIterrupibly
- lock被中斷不會拋出異常, 而 lockIterruptibly會拋出異常.
熟悉的方法
1. void lock(): 執(zhí)行此方法時, 如果鎖處于空閑狀態(tài), 當前線程將獲取到鎖. 相反, 如果鎖已經 被其他線程持有, 將禁用當前線程, 直到當前線程獲取到鎖. 2. boolean tryLock():如果鎖可用, 則獲取鎖, 并立即返回true, 否則返回false. 該方法和 lock()的區(qū)別在于, tryLock()只是"試圖"獲取鎖, 如果鎖不可用, 不會導致當前線程被禁用, 當前線程仍然繼續(xù)往下執(zhí)行代碼. 而lock()方法則是一定要獲取到鎖, 如果鎖不可用, 就一 直等待, 在未獲得鎖之前,當前線程并不繼續(xù)向下執(zhí)行. 3. void unlock():執(zhí)行此方法時, 當前線程將釋放持有的鎖. 鎖只能由持有者釋放, 如果線程 并不持有鎖, 卻執(zhí)行該方法, 可能導致異常的發(fā)生. 4. Condition newCondition():條件對象,獲取等待通知組件。該組件和當前的鎖綁定, 當前線程只有獲取了鎖,才能調用該組件的await()方法,而調用后,當前線程將縮放鎖。不熟悉的方法
1. getHoldCount() :查詢當前線程保持此鎖的次數,也就是執(zhí)行此線程執(zhí)行l(wèi)ock 方法的次 數。 2. getQueueLength():返回正等待獲取此鎖的線程估計數,比如啟動10 個線程,1 個 線程獲得鎖,此時返回的是9 3. getWaitQueueLength:(Condition condition)返回等待與此鎖相關的給定條件的線 程估計數。比如10 個線程,用同一個condition 對象,并且此時這10 個線程都執(zhí)行了 condition 對象的await 方法,那么此時執(zhí)行此方法返回10 4. hasWaiters(Condition condition) : 查詢是否有線程等待與此鎖有關的給定條件 (condition),對于指定contidion 對象,有多少線程執(zhí)行了condition.await 方法 5. hasQueuedThread(Thread thread):查詢給定線程是否等待獲取此鎖 6. hasQueuedThreads():是否有線程等待此鎖 7. isFair():該鎖是否公平鎖 8. isHeldByCurrentThread(): 當前線程是否保持鎖鎖定,線程的執(zhí)行l(wèi)ock 方法的前后分 別是false 和true 9. isLock():此鎖是否有任意線程占用 10. lockInterruptibly():如果當前線程未被中斷,獲取鎖 11. tryLock():嘗試獲得鎖,僅在調用時鎖未被線程占用,獲得鎖 12. tryLock(long timeout TimeUnit unit):如果鎖在給定等待時間內沒有被另一個線程保持, 則獲取該鎖。非公平鎖 : JVM隨機或就近分配鎖的機制。ReentrantLock(false) 構造函數設置公平/非公平
公平鎖 : 先申請鎖的先得到鎖。ReentrantLock(true)
-
Semaphore 信號量
閾值設置,運行多少個線程同時操作資源。
閾值設置為 1 即互斥信號量 減1 : Semaphore.acquire()
信號量 加1 : Semaphore.release()
-
AtomicInteger 原子操作Integer
還可以通過AtomicReference<V>將對象的所有操作轉化為原子操作。
保證操作的原子性 ,效率比lock高
-
ReadWriteLock 讀寫鎖
讀鎖: 讀取的時候保證多個人讀,不能同時寫
寫鎖 : 寫入的時候保證一個人寫,且不能讀
-
鎖優(yōu)化
- 減少鎖持有時間
- 減少鎖粒度 , 降低鎖的競爭
- 鎖分離 , 例如讀寫鎖
- 鎖粗化 如果對一個鎖不停請求,同步釋放也會作大量
- 鎖消除 編譯器干的事
十、 線程的基本方法
-
wait
線程阻塞 , 釋放鎖,進入wait set 線程等待池
-
sleep
線程休眠,不釋放鎖 , 進入time-wating狀態(tài)
-
yeld
線程讓步 , 讓出cpu使用權,與其他線程競爭時間片
-
interrupt()
線程中斷 , 但不直接中斷線程。
- 給出通知信號 , 使得time-waiting狀態(tài)的線程拋出interruptException異常。中斷位會清零。
- 設置中斷位為 1
-
join等待其他線程終止
其他線程調用join方法,當前線程阻塞 。 不放棄鎖 , 等到相關線程結束,轉為就緒狀態(tài)。
-
Object.notify
線程喚醒 , 喚醒對象監(jiān)視器上等待的單個線程,競爭cpu使用權。
為什么notify和wait都在object類上?
答:因為notify和 wait需要作用在同一個鎖上。而鎖可以是任意對象,可以被任意對象調用的方法是定義在object類中。
-
其他方法:
1. sleep():強迫一個線程睡眠N毫秒。 2. isAlive(): 判斷一個線程是否存活。 3. join(): 等待線程終止。 4. activeCount(): 程序中活躍的線程數。 5. enumerate(): 枚舉程序中的線程。 6. currentThread(): 得到當前線程。 7. isDaemon(): 一個線程是否為守護線程。 8. setDaemon(): 設置一個線程為守護線程。(用戶線程和守護線程的區(qū)別在于,是否等待主線 程依賴于主線程結束而結束) 9. setName(): 為線程設置一個名稱。 10. wait(): 強迫一個線程等待。 11. setPriority(): 設置一個線程的優(yōu)先級。 12. getPriority()::獲得一個線程的優(yōu)先級。
十一線程上下文切換
線程上下文切換,同一顆cpu上 ,任務狀態(tài)的保存和再加載
-
上下文
某一時間點,cpu寄存器和計數器內容
-
PCB 進程控制塊-切換幀
表示進程狀態(tài)的信息塊。
-
上下文切換活動
- 掛起進程(線程) , 將進程信息存儲在內存
- 內存中檢索下一個進程上下文,并在CPU寄存器中恢復
- 跳轉到PC ,運行程序
-
引起上下文切換原因
- 時間片用完
- IO阻塞
- 多任務搶占鎖,未獲得鎖
- 硬件中斷
十二線程池原理
主要特點 : 線程復用 , 控制最大并發(fā)數 , 線程管理
-
線程復用: ?
在Thread strat() 方法中不斷循環(huán)調用傳遞過來的Runnable對象 , 并使用Queue組織這些Runnable對象。
-
ThreadPoolExecutor
構造方法如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit , workQueue, Executors.defaultThreadFactory(), defaultHandler); }1. corePoolSize:指定了線程池中的線程數量。 2. maximumPoolSize:指定了線程池中的大線程數量。 3. keepAliveTime:當前線程池數量超過corePoolSize時,多余的空閑線程的存活時間,即多少時間內會被銷毀。 4. unit:keepAliveTime的單位。 5. workQueue:任務隊列,被提交但尚未被執(zhí)行的任務。 6. threadFactory:線程工廠,用于創(chuàng)建線程,一般用默認的即可。 7. handler:拒絕策略,當任務太多來不及處理,如何拒絕任務。 -
線程池工作過程
1. 線程池剛創(chuàng)建時,里面沒有一個線程。任務隊列是作為參數傳進來的。不過,就算隊列里面 有任務,線程池也不會馬上執(zhí)行它們。 2. 當調用 execute() 方法添加一個任務時,線程池會做如下判斷: a) 如果正在運行的線程數量小于 corePoolSize,那么馬上創(chuàng)建線程運行這個任務; b) 如果正在運行的線程數量大于或等于 corePoolSize,那么將這個任務放入隊列; c) 如果這時候隊列滿了,而且正在運行的線程數量小于 maximumPoolSize,那么還是要 創(chuàng)建非核心線程立刻運行這個任務; d) 如果隊列滿了,而且正在運行的線程數量大于或等于 maximumPoolSize,那么線程池 會拋出異常RejectExecutionException。 3. 當一個線程完成任務時,它會從隊列中取下一個任務來執(zhí)行。 4. 當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運 行的線程數大于 corePoolSize,那么這個線程就被停掉。 所以線程池的所有任務完成后,它 終會收縮到 corePoolSize 的大小。
