多線程筆記
1.線程的6種狀態(Thread.State)
(1)New(新創建):
new新線程,還未運行。
(2)Runnable(可運行)
調用start方法後。
(3)Blocked(被阻塞)
當前線程試圖獲取內部的對象鎖但該鎖被其他線程持有時,該線程進入阻塞狀態;當其他線程釋放該鎖,且線程調度器允許本線程持有它的時候變成非阻塞狀態。
(4)Waiting(等待)
當線程等待另一個線程通知線程調度器某個條件時,它自己進入等待狀態。如執行(Object)wait、(Thread)join方法 ,或是等待java.util.concurrent庫中的Lock或Condition時。
(5)Timed Waiting(計時等待)lock
執行帶有超時參數的方法時,如(Thread)sleep、(Object)wait、(Thread)join、(Lock)tryLock、(Condition)await。
(6)Terminated(終止)
a.run方法正常退出而自然死亡;
b.由於未捕獲的異常導致run方法異常退出而意外死亡。
2.線程屬性
2.1線程優先級
MIN_PRIORITY=1,MAX_PRORITY=10,NORM_PRIORITY=5
當線程調度器有機會選擇新線程時,優先選擇優先級高的。
線程優先級是高度依賴於宿主系統的,Java線程優先級映射到宿主系統時可能更多也可能更少。
2.2守護線程
setDaemon(true);//為其他線程提供服務;在線程啟動之前調用
註:守護線程應該永遠不去訪問固有資源,如文件、數據庫等,因為它可能會在任何時候甚至一個操作的中間發生中斷。
2.3未捕獲異常處理器
線程的run方法不能拋出任何被檢測的異常,而未檢測異常又會導致線程終止。
不需要用catch處理可以被傳播的異常,在線程死亡之前,異常被傳遞到一個用於未捕獲異常的處理器。
3.同步
3.1原因:多線程共享同一數據
3.2實現方式
方式(一)synchronizd關鍵字
方式(二)java.util.concurrent.locks.Lock接口,實現類有:
- ReentrantLock
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock
註:解鎖(unlock)語句應在finally塊中。
3.3鎖對象
Reentrantlock();
鎖是可重入的,即線程可以重復獲得已持有的鎖,鎖保持一個持有計數來跟蹤lock方法的嵌套調用。
3.4條件對象(條件變量)
原因:要用一個變量來管理那些已進入臨界區,即獲得了鎖卻不能做有用工作的線程。
Condition c=bankLock.newCondition();
當條件不滿足時,調用c.await()方法,線程進入該條件的等待集。
註意與等待鎖的線程區分:當鎖可用時,該線程不能馬上解除阻塞,相反,它要阻塞直到其他線程調用同一條件的signalAll()方法。
signal()方法隨機解除等待集中某個線程的阻塞狀態。
小結:
提供高度的鎖定控制。
- 鎖用來保護代碼片段,任何時刻只能有一個線程執行被保護的代碼;
- 鎖可以管理試圖進入被保護代碼段的線程;
- 鎖可以擁有一個或多個相關的條件對象;
- 每個條件對象管理那些已進入被保護代碼段但還不能運行的線程。
3.5 synchronized關鍵字
Java中的每個對象都有一個內部鎖,並且該鎖有一個內部條件。
wait()/notifyAll()方法相當於condition.await()/signalAll()方法。
靜態方法也可以聲明為synchronized(類對象的內部鎖)。
所以,synchronized靜態方法有什麽意義嗎?
內部鎖和條件的局限性:
- 不能中斷一個正在試圖獲得鎖的線程;
- 試圖獲得鎖時不能設定超時;
- 每個鎖僅有單一條件,可能是不夠的。
建議:既不使用Lock/Condition,也不實用synchronized;推薦使用java.util.concurrent 包中的一種機制。
3.6 同步阻塞
synchronized(obj){
...
obj.method1();
obj.method2();
}
又叫客戶端鎖定,必須保證(obj)類的所有可修改方法都使用內部鎖。
因此,該方法很脆弱,不推薦使用。
3.7 監視器
在程序員不需要考慮如何加鎖的情況下保證多線程安全。
監視器的特性:
- 監視器是只包含私有域的類;
- 每個監視器類的對象有一個相關的鎖;
- 使用該鎖對所有的方法進行加鎖;
- 該鎖可以有任意多個相關條件。
Java對象的synchronized類似監視器,但不同(安全性下降):
- 域不要求必須是private;
- 方法不要求必須是synchronized;
- 內部鎖對客戶是可用的。
3.8 volatile域
提供一種免鎖機制;編譯器和虛擬機知道該域可能被另一個線程並發更新。
不提供原子性。
3.9 原子性
如果對共享對象除了賦值之外沒有別的操作,可以將其聲明為volatile。
3.10 死鎖
兩個或兩個以上的線程在執行過程中,因爭奪資源而產生的一種互相等待的現象。
產生死鎖的4個必要條件:
- 互斥條件:一個資源同時只能有一個線程訪問;
- 請求與保持條件:一個線程請求阻塞時,對於已獲得的資源保持不放;
- 不可剝奪條件:一個線程獲得的資源在只用完之前不能被剝奪,只能在使用完畢後釋放;
- 循環等待條件:若幹線程形成頭尾相接的循環等待資源關系。
解決死鎖的方法:
- 預防死鎖:設置限制條件,破壞產生死鎖的必要條件(之一);效率降低;
- 死鎖避免:允許前三個必要條件,但通過明智的選擇確保永遠不會到達死鎖點;比死鎖預防允許更多的並發;
- 死鎖檢測:不須采取任何限制措施,允許發生死鎖。但通過系統設置的檢測機構及時檢測死鎖的發生,並精確地確定死鎖相關的線程和資源,采取相關措施將死鎖清除;
- 死鎖解除:與死鎖檢測相配套,常用方法:撤銷或掛起一些線程,以便回收資源分配給已阻塞的線程;死鎖檢測和解除會獲得較好的資源利用率和吞吐量,但實現難。
3.11 讀寫鎖
- java.util.concurrent.locks.ReentrantReadWriteLock
對所有的獲取方法加讀鎖——readLock()
對所有的修改方法加寫鎖——writeLock()
4. 阻塞隊列
生產者、消費者
多線程程序中,使用不會拋出異常的poll、peek和offer方法。
- java.util.concurrent.LinkedBlockingQueue<E> 容量無上界,但也可以指定最大容量。
- java.util.concurrent.LinkedBlockingDeque<E> 雙端版本
- java.util.concurrent.ArrayBlockingQueue<E> 構造時需要指定容量,並且有一個可選參數用來指定是否需要公平性。
- java.util.concurrent.PriorityBlockingQueue<E> 是優先級隊列,而非先進先出。
5.線程安全的集合
java.util.concurrent包提供了映射表、有序集和隊列的高效實現,如:
- java.util.concurrent.ConcurrentHashMap<K,V>
- java.util.concurrent.ConcurrentSkipListMap<K,V>
- java.util.concurrent.ConcurrentSkipListSet<E>
- java.util.concurrent.ConcurrentLinkedQueue<E>
並發的散列表可以高效的支持大量讀者和一定數量的寫者;保證原子性。
6.執行器
線程池(Thread pool):程序中創建了大量生命期很短的線程時使用;減少並發線程的數目。
將一個Runnable對象交給線程池,就會有一個線程調用run方法。
執行器(Executor)類有許多靜態工廠方法用來構建線程池。
多線程筆記