java多線程核心技術梳理(附源碼)

來源:互聯網
上載者:User
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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.