java多線程核心技術梳理(附源碼)
標籤: java
java多線程核心技術梳理附源碼 寫在前面 java多線程 對象及變數的並發訪問 線程間通訊 Lock的使用 定時器 單例模式與多線程 拾遺補增 參考資料
本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變數的並發訪問,線程間通訊,lock的使用,定時器,單例模式,以及線程狀態與線程組。 寫在前面
花了一周時間閱讀《java多線程編程核心技術》(高洪岩 著),本文算是此書的整理歸納,書中幾乎所有樣本,我都親手敲了一遍,並上傳到了我的github上,有興趣的朋友可以到我的github下載。源碼採用maven構建,多線程這部分源碼位於java-multithread模組中。 倉庫地址:java-learning git clone: git@github.com:brianway/java-learning.git java多線程
基礎知識 建立線程的兩種方式:1.繼承Thread類,2.實現Runnable介面。具體兩者的聯絡可以參考我之前的博文《java基礎鞏固筆記(5)-多線程之傳統多線程》 一些基本API:isAlive(),sleep(),getId(),yield()等。
isAlive()測試線程是否處於活動狀態 sleep()讓“正在執行的線程”休眠 getId()取得線程唯一標識 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類可在子線程中取得父線程繼承下來的值。 Lock的使用 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()對所有線程對象設定異常處理器 參考資料 淺談Java中的鎖 java synchronized關鍵字的用法 Java Thread(線程)案例詳解sleep和wait的區別
作者@brianway更多文章:個人網站 | CSDN | oschina