本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的併發訪問,線程間通訊,lock的使用,定時器,單例模式,以及線程狀態與線程組。html
花了一週時間閱讀《java多線程編程核心技術》(高洪巖 著),本文算是此書的整理概括,書中幾乎全部示例,我都親手敲了一遍,並上傳到了個人github上,有興趣的朋友能夠到個人github下載。源碼採用maven構建,多線程這部分源碼位於java-multithread
模塊中。java
倉庫地址:java-learninggit
git clone:
git@github.com:brianway/java-learning.git
github
基礎知識編程
建立線程的兩種方式:1.繼承Thread類,2.實現Runnable接口。具體二者的聯繫能夠參考我以前的博文《java基礎鞏固筆記(5)-多線程之傳統多線程》api
一些基本API:isAlive(),sleep(),getId(),yield()等。安全
isAlive()
測試線程是否處於活動狀態多線程
sleep()
讓「正在執行的線程」休眠併發
getId()
取得線程惟一標識oracle
yield()
放棄當前的CPU資源
棄用的API:stop()
,suspend()
,resume()
等,已經棄用了,由於可能產生數據不一樣步等問題。
中止線程的幾種方式:
使用退出標識,使線程正常退出,即run方法完成。
使用interrupt方法中斷線程
線程的優先級:繼承性,規則性,隨機性
線程的優先級具備繼承性. 如,線程A啓動線程B,則B和A優先級同樣
線程的優先級具備規則性. CPU儘可能傾向於把資源優先級高的線程
線程的優先級具備隨機性. 優先級不等同於執行順序,兩者關係不肯定
java中的兩種線程:用戶線程和守護(Daemon)線程。
守護線程:進程中不存在非守護線程時,守護線程自動銷燬。典型例子如:垃圾回收線程。
比較和辨析
某個線程與當前線程:當前線程則是指正在運行的那個線程,可由currentThread()
方法返回值肯定。例如,直接在main方法裏調用run方法,和調用線程的start方法,打印出的當前線程結果是不一樣的。
interrupted()
和isInterrupted()
interrupted()
是類的靜態方法,測試當前線程是否已是中斷狀態,執行後具備將狀態標誌清除爲false的功能。
isInterrupted()
是類的實例方法,測試Thread對象是否已是中斷狀態,但不清楚狀態標誌。
sleep()
和wait()
區別:
sleep()是Thread類的static(靜態)的方法;wait()方法是Object類裏的方法
sleep()睡眠時,保持對象鎖,仍然佔有該鎖;wait()睡眠時,釋放對象鎖
在sleep()休眠時間期滿後,該線程不必定會當即執行,這是由於其它線程可能正在運行並且沒有被調度爲放棄執行,除非此線程具備更高的優先級;wait()使用notify或者notifyAlll或者指定睡眠時間來喚醒當前等待池中的線程
wait()必須放在synchronized block中,不然會在runtime時扔出java.lang.IllegalMonitorStateException
異常
方法 | 是否釋放鎖 | 備註 |
---|---|---|
wait | 是 | wait和notify/notifyAll是成對出現的, 必須在synchronize塊中被調用 |
sleep | 否 | 可以使低優先級的線程得到執行機會 |
yield | 否 | yield方法使當前線程讓出CPU佔有權, 但讓出的時間是不可設定的 |
synchronized
關鍵字
調用用關鍵字synchronized聲明的方法是排隊運行的。但假如線程A持有某對象的鎖,那線程B異步調用非synchronized類型的方法不受限制。
synchronized鎖重入:一個線程獲得對象鎖後,再次請求此對象鎖時是能夠獲得該對象的鎖的。同時,子類可經過「可重入鎖」調用父類的同步方法。
同步不具備繼承性。
synchronized使用的「對象監視器」是一個,即必須是同一個對象
synchronized同步方法和synchronized同步代碼塊。
對其餘synchronized同步方法或代碼塊調用呈阻塞狀態。
同一時間只有一個線程可執行synchronized方法/代碼塊中的代碼
synchronized(非this對象x),將x對象做爲「對象監視器」
當多個線程同時執行synchronized(x){}
同步代碼塊時呈同步效果
當其餘線程執行x對象中synchronizd同步方法時呈同步效果
當其餘線程執行x對象方法裏的synchronized(this)代碼塊時呈同步效果
靜態同步synchronized方法與synchronized(class)代碼塊:對當前對應的class類進行持鎖。
線程的私有堆棧圖
volatile關鍵字:主要做用是使變量在多個線程間可見。加volatile關鍵字可強制性從公共堆棧進行取值,而不是從線程私有數據棧中取得變量的值
在方法中while循環中設置狀態位(不加volatile關鍵字),在外面把狀態位置位並不可行,循環不會中止,好比JVM在-server模式。
緣由:是私有堆棧中的值和公共堆棧中的值不一樣步
volatile增長了實例變量在多個線程間的可見性,但不支持原子性
原子類:一個原子類型就是一個原子操做可用的類型,可在沒有鎖的狀況下作到線程安全。但原子類也不是徹底安全,雖然原子操做是安全的,可方法間的調用卻不是原子的,須要用同步。
讀取公共內存圖
辨析和零散補充
synchronized靜態方法與非靜態方法:synchronized關鍵字加static靜態方法上是給Class類上鎖,能夠對類的全部實例對象起做用;synchronized關鍵字加到非static靜態方法上是給對象上鎖,對該對象起做用。這兩個鎖不是同一個鎖。
synchronized和volatile比較
1)關鍵字volatile是線程同步的輕量級實現,性能比synchronized好,且volatile只能修飾變量,synchronized可修飾方法和代碼塊。
2)多線程訪問volatile不會發生阻塞,synchronized會出現阻塞
3)volatile能保證數據可見性,不保證原子性;synchronized能夠保證原子性,也能夠間接保證可見性,由於synchronized會將私有內存和公共內存中的數據作同步。
4)volatile解決的是變量在多個線程間的可見性,synchronized解決的是多個線程訪問資源的同步性。
String常量池特性,故大多數狀況下,synchronized代碼塊都不適用String做爲鎖對象。
多線程死鎖。使用JDK自帶工具,jps命令+jstack命令監測是否有死鎖。
內置類與靜態內置類。
鎖對象的的改變。
一個線程出現異常時,其所持有的鎖會自動釋放。
變量在內存中的工做過程圖
等待/通知機制:wait()
和notify()
/notifyAll()
。wait使線程中止運行,notify使中止的線程繼續運行。
wait()
:將當前執行代碼的線程進行等待,置入"預執行隊列"。
在調用wait()以前,線程必須得到該對象的對象級別鎖;
執行wait()方法後,當前線程當即釋放鎖;
從wait()返回前,線程與其餘線程競爭從新得到鎖
當線程呈wait()狀態時,調用線程的interrup()方法會出現InterrupedException異常
wait(long)
是等待某一時間內是否有線程對鎖進行喚醒,超時則自動喚醒。
notify()
:通知可能等待該對象的對象鎖的其餘線程。隨機挑選一個呈wait狀態的線程,使它等待獲取該對象的對象鎖。
在調用notify()以前,線程必須得到該對象的對象級別鎖;
執行完notify()方法後,不會立刻釋放鎖,要直到退出synchronized代碼塊,當前線程纔會釋放鎖。
notify()一次只隨機通知一個線程進行喚醒
notifyAll()
和notify()
差很少,只不過是使全部正在等待隊中等待同一共享資源的「所有」線程從等待狀態退出,進入可運行狀態。
每一個鎖對象有兩個隊列:就緒隊列和阻塞隊列。
就緒隊列:存儲將要得到鎖的線程
阻塞隊列:存儲被阻塞的的線程
生產者/消費者模式
「假死」:線程進入WAITING等待狀態,呈假死狀態的進程中全部線程都呈WAITING狀態。
假死的主要緣由:有可能連續喚醒同類。notify喚醒的不必定是異類,也許是同類,如「生產者」喚醒「生產者」。
解決假死:將notify()改成notifyAll()
wait條件改變,可能出現異常,須要將if改爲while
經過管道進行線程間通訊:一個線程發送數據到輸出管道,另外一個線程從輸入管道讀數據。
字節流:PipedInputStream
和PipedOutputStream
字符流:PipedReader
和PipedWriter
join()
:等待線程對象銷燬,具備使線程排隊運行的做用。
join()與interrupt()方法彼此遇到會出現異常。
join(long)
可設定等待的時間
join
與synchronized
的區別:join在內部使用wait()方法進行等待;synchronized使用的是「對象監視器」原理做爲同步
join(long)
與sleep(long)
的區別:join(long)內部使用wait(long)實現,因此join(long)具備釋放鎖的特色;Thread.sleep(long)不釋放鎖。
ThreadLocal
類:每一個線程綁定本身的值
覆寫該類的initialValue()
方法可使變量初始化,從而解決get()返回null的問題
InheritableThreadLocal
類可在子線程中取得父線程繼承下來的值。
ReentrantLock
類:實現線程之間的同步互斥,比synchronized更靈活
lock()
,調用了的線程就持有了「對象監視器」,效果和synchronized同樣
使用Condition
實現等待/通知:比wait()和notify()/notyfyAll()更靈活,好比可實現多路通知。
調用condition.await()前須先調用lock.lock()得到同步監視器
Object與Condition方法對比
Object | Condition |
---|---|
wait() | await() |
wait(long timeout) | await(long time,TimeUnit unit) |
notify() | signal() |
notifyAll() | signalAll() |
一些API
方法 | 說明 |
---|---|
int getHoldCount() |
查詢當前線程保持此鎖定的個數,即調用lock()方法的次數 |
int getQueueLength() |
返回正在等待獲取此鎖定的線程估計數 |
int getWaitQueueLength(Condition condition) |
返回等待與此鎖定相關的給定條件Conditon的線程估計數 |
boolean hasQueueThread(Thread thread) |
查詢指定的線程是否正在等待獲取此鎖定 |
boolean hasQueueThreads() |
查詢是否有線程正在等待獲取此鎖定 |
boolean hasWaiters(Condition) |
查詢是否有線程正在等待與此鎖定有關的condition條件 |
boolean isFair() |
判斷是否是公平鎖 |
boolean isHeldByCurrentThread() |
查詢當前線程是否保持此鎖定 |
boolean isLocked() |
查詢此鎖定是否由任意線程保持 |
void lockInterruptibly() |
若是當前線程未被中斷,則獲取鎖定,若是已經被中斷則出現異常 |
boolean tryLock() |
僅在調用時鎖定未被另外一個線程保持的狀況下,才獲取該鎖定 |
boolean tryLock(long timeout,TimeUnit unit) |
若是鎖定在給定等待時間內沒有被另外一個線程保持,且當前線程未被中斷,則獲取該鎖定 |
公平鎖與非公平鎖
公平鎖表示線程獲取鎖的順序是按照加鎖的順序來分配的,即FIFO先進先出。
非公平鎖是一種獲取鎖的搶佔機制,隨機得到鎖。
ReentrantReadWriteLock
類
讀讀共享
寫寫互斥
讀寫互斥
寫讀互斥
經常使用API
方法 | 說明 |
---|---|
schedule(TimerTask task, Date time) | 在指定的日期執行某一次任務 |
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 在指定的日期以後按指定的間隔週期,無限循環的執行某一任務 |
schedule(TimerTask task, long delay) | 以執行此方法的當前時間爲參考時間,在此時間基礎上延遲指定的毫秒數後執行一次TimerTask任務 |
schedule(TimerTask task, long delay, long period) | 以執行此方法的當前時間爲參考時間,在此時間基礎上延遲指定的毫秒數,再以某一間隔時間無限次數地執行某一TimerTask任務 |
schedule
和scheduleAtFixedRate
的區別:schedule不具備追趕執行性;scheduleAtFixedRate具備追趕執行性
當即加載/「餓漢模式」:調用方法前,實例已經被建立了。經過靜態屬性new實例化實現的
延遲加載/「懶漢模式」:調用get()方法時實例才被建立。最多見的實現辦法是在get()方法中進行new實例化
缺點:多線程環境中,會出問題
解決方法
聲明synchronized關鍵字,但運行效率很是低下
同步代碼塊,效率也低
針對某些重要代碼(實例化語句)單獨同步,效率提高,但會出問題
使用DCL雙檢查鎖
使用enum枚舉數據類型實現單例模式
方法與狀態關係示意圖
線程的狀態:Thread.State
枚舉類,參考官網APIEnum Thread.State
線程組:線程組中能夠有線程對象,也能夠有線程組,組中還能夠有線程。可批量管理線程或線程組對象。
SimpleDateFormat
非線程安全,解決辦法有:
建立多個SimpleDateFormat類的實例
使用ThreadLocal類
線程組出現異常的處理
setUncaughtExceptionHandler()
給指定線程對象設置異常處理器
setDefaultUncaughtExceptionHandler()
對全部線程對象設置異常處理器