摘要:本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問,線程間通信,的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。源碼采用構(gòu)建,多線程這部分源碼位于模塊中。通知可能等待該對(duì)象的對(duì)象鎖的其他線程。
本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問,線程間通信,lock的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。
寫在前面花了一周時(shí)間閱讀《java多線程編程核心技術(shù)》(高洪巖 著),本文算是此書的整理歸納,書中幾乎所有示例,我都親手敲了一遍,并上傳到了我的github上,有興趣的朋友可以到我的github下載。源碼采用maven構(gòu)建,多線程這部分源碼位于java-multithread模塊中。
java多線程倉庫地址:java-learning
git clone: [email protected]:brianway/java-learning.git
基礎(chǔ)知識(shí)
創(chuàng)建線程的兩種方式:1.繼承Thread類,2.實(shí)現(xiàn)Runnable接口。具體兩者的聯(lián)系可以參考我之前的博文《java基礎(chǔ)鞏固筆記(5)-多線程之傳統(tǒng)多線程》
一些基本API:isAlive(),sleep(),getId(),yield()等。
isAlive()測(cè)試線程是否處于活動(dòng)狀態(tài)
sleep()讓“正在執(zhí)行的線程”休眠
getId()取得線程唯一標(biāo)識(shí)
yield()放棄當(dāng)前的CPU資源
棄用的API:stop(),suspend(),resume()等,已經(jīng)棄用了,因?yàn)榭赡墚a(chǎn)生數(shù)據(jù)不同步等問題。
停止線程的幾種方式:
使用退出標(biāo)識(shí),使線程正常退出,即run方法完成。
使用interrupt方法中斷線程
線程的優(yōu)先級(jí):繼承性,規(guī)則性,隨機(jī)性
線程的優(yōu)先級(jí)具有繼承性. 如,線程A啟動(dòng)線程B,則B和A優(yōu)先級(jí)一樣
線程的優(yōu)先級(jí)具有規(guī)則性. CPU盡量傾向于把資源優(yōu)先級(jí)高的線程
線程的優(yōu)先級(jí)具有隨機(jī)性. 優(yōu)先級(jí)不等同于執(zhí)行順序,二者關(guān)系不確定
java中的兩種線程:用戶線程和守護(hù)(Daemon)線程。
守護(hù)線程:進(jìn)程中不存在非守護(hù)線程時(shí),守護(hù)線程自動(dòng)銷毀。典型例子如:垃圾回收線程。
比較和辨析
某個(gè)線程與當(dāng)前線程:當(dāng)前線程則是指正在運(yùn)行的那個(gè)線程,可由currentThread()方法返回值確定。例如,直接在main方法里調(diào)用run方法,和調(diào)用線程的start方法,打印出的當(dāng)前線程結(jié)果是不同的。
interrupted()和isInterrupted()
interrupted()是類的靜態(tài)方法,測(cè)試當(dāng)前線程是否已經(jīng)是中斷狀態(tài),執(zhí)行后具有將狀態(tài)標(biāo)志清除為false的功能。
isInterrupted()是類的實(shí)例方法,測(cè)試Thread對(duì)象是否已經(jīng)是中斷狀態(tài),但不清楚狀態(tài)標(biāo)志。
sleep()和wait()區(qū)別:
sleep()是Thread類的static(靜態(tài))的方法;wait()方法是Object類里的方法
sleep()睡眠時(shí),保持對(duì)象鎖,仍然占有該鎖;wait()睡眠時(shí),釋放對(duì)象鎖
在sleep()休眠時(shí)間期滿后,該線程不一定會(huì)立即執(zhí)行,這是因?yàn)槠渌€程可能正在運(yùn)行而且沒有被調(diào)度為放棄執(zhí)行,除非此線程具有更高的優(yōu)先級(jí);wait()使用notify或者notifyAlll或者指定睡眠時(shí)間來喚醒當(dāng)前等待池中的線程
wait()必須放在synchronized block中,否則會(huì)在runtime時(shí)扔出java.lang.IllegalMonitorStateException異常
方法 | 是否釋放鎖 | 備注 |
---|---|---|
wait | 是 | wait和notify/notifyAll是成對(duì)出現(xiàn)的, 必須在synchronize塊中被調(diào)用 |
sleep | 否 | 可使低優(yōu)先級(jí)的線程獲得執(zhí)行機(jī)會(huì) |
yield | 否 | yield方法使當(dāng)前線程讓出CPU占有權(quán), 但讓出的時(shí)間是不可設(shè)定的 |
synchronized關(guān)鍵字
調(diào)用用關(guān)鍵字synchronized聲明的方法是排隊(duì)運(yùn)行的。但假如線程A持有某對(duì)象的鎖,那線程B異步調(diào)用非synchronized類型的方法不受限制。
synchronized鎖重入:一個(gè)線程得到對(duì)象鎖后,再次請(qǐng)求此對(duì)象鎖時(shí)是可以得到該對(duì)象的鎖的。同時(shí),子類可通過“可重入鎖”調(diào)用父類的同步方法。
同步不具有繼承性。
synchronized使用的“對(duì)象監(jiān)視器”是一個(gè),即必須是同一個(gè)對(duì)象
synchronized同步方法和synchronized同步代碼塊。
對(duì)其他synchronized同步方法或代碼塊調(diào)用呈阻塞狀態(tài)。
同一時(shí)間只有一個(gè)線程可執(zhí)行synchronized方法/代碼塊中的代碼
synchronized(非this對(duì)象x),將x對(duì)象作為“對(duì)象監(jiān)視器”
當(dāng)多個(gè)線程同時(shí)執(zhí)行synchronized(x){}同步代碼塊時(shí)呈同步效果
當(dāng)其他線程執(zhí)行x對(duì)象中synchronizd同步方法時(shí)呈同步效果
當(dāng)其他線程執(zhí)行x對(duì)象方法里的synchronized(this)代碼塊時(shí)呈同步效果
靜態(tài)同步synchronized方法與synchronized(class)代碼塊:對(duì)當(dāng)前對(duì)應(yīng)的class類進(jìn)行持鎖。
線程的私有堆棧圖
volatile關(guān)鍵字:主要作用是使變量在多個(gè)線程間可見。加volatile關(guān)鍵字可強(qiáng)制性從公共堆棧進(jìn)行取值,而不是從線程私有數(shù)據(jù)棧中取得變量的值
在方法中while循環(huán)中設(shè)置狀態(tài)位(不加volatile關(guān)鍵字),在外面把狀態(tài)位置位并不可行,循環(huán)不會(huì)停止,比如JVM在-server模式。
原因:是私有堆棧中的值和公共堆棧中的值不同步
volatile增加了實(shí)例變量在多個(gè)線程間的可見性,但不支持原子性
原子類:一個(gè)原子類型就是一個(gè)原子操作可用的類型,可在沒有鎖的情況下做到線程安全。但原子類也不是完全安全,雖然原子操作是安全的,可方法間的調(diào)用卻不是原子的,需要用同步。
讀取公共內(nèi)存圖
辨析和零散補(bǔ)充
synchronized靜態(tài)方法與非靜態(tài)方法:synchronized關(guān)鍵字加static靜態(tài)方法上是給Class類上鎖,可以對(duì)類的所有實(shí)例對(duì)象起作用;synchronized關(guān)鍵字加到非static靜態(tài)方法上是給對(duì)象上鎖,對(duì)該對(duì)象起作用。這兩個(gè)鎖不是同一個(gè)鎖。
synchronized和volatile比較
1)關(guān)鍵字volatile是線程同步的輕量級(jí)實(shí)現(xiàn),性能比synchronized好,且volatile只能修飾變量,synchronized可修飾方法和代碼塊。
2)多線程訪問volatile不會(huì)發(fā)生阻塞,synchronized會(huì)出現(xiàn)阻塞
3)volatile能保證數(shù)據(jù)可見性,不保證原子性;synchronized可以保證原子性,也可以間接保證可見性,因?yàn)?strong>synchronized會(huì)將私有內(nèi)存和公共內(nèi)存中的數(shù)據(jù)做同步。
4)volatile解決的是變量在多個(gè)線程間的可見性,synchronized解決的是多個(gè)線程訪問資源的同步性。
String常量池特性,故大多數(shù)情況下,synchronized代碼塊都不適用String作為鎖對(duì)象。
多線程死鎖。使用JDK自帶工具,jps命令+jstack命令監(jiān)測(cè)是否有死鎖。
內(nèi)置類與靜態(tài)內(nèi)置類。
鎖對(duì)象的的改變。
一個(gè)線程出現(xiàn)異常時(shí),其所持有的鎖會(huì)自動(dòng)釋放。
變量在內(nèi)存中的工作過程圖
線程間通信
等待/通知機(jī)制:wait()和notify()/notifyAll()。wait使線程停止運(yùn)行,notify使停止的線程繼續(xù)運(yùn)行。
wait():將當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,置入"預(yù)執(zhí)行隊(duì)列"。
在調(diào)用wait()之前,線程必須獲得該對(duì)象的對(duì)象級(jí)別鎖;
執(zhí)行wait()方法后,當(dāng)前線程立即釋放鎖;
從wait()返回前,線程與其他線程競爭重新獲得鎖
當(dāng)線程呈wait()狀態(tài)時(shí),調(diào)用線程的interrup()方法會(huì)出現(xiàn)InterrupedException異常
wait(long)是等待某一時(shí)間內(nèi)是否有線程對(duì)鎖進(jìn)行喚醒,超時(shí)則自動(dòng)喚醒。
notify():通知可能等待該對(duì)象的對(duì)象鎖的其他線程。隨機(jī)挑選一個(gè)呈wait狀態(tài)的線程,使它等待獲取該對(duì)象的對(duì)象鎖。
在調(diào)用notify()之前,線程必須獲得該對(duì)象的對(duì)象級(jí)別鎖;
執(zhí)行完notify()方法后,不會(huì)馬上釋放鎖,要直到退出synchronized代碼塊,當(dāng)前線程才會(huì)釋放鎖。
notify()一次只隨機(jī)通知一個(gè)線程進(jìn)行喚醒
notifyAll()和notify()差不多,只不過是使所有正在等待隊(duì)中等待同一共享資源的“全部”線程從等待狀態(tài)退出,進(jìn)入可運(yùn)行狀態(tài)。
每個(gè)鎖對(duì)象有兩個(gè)隊(duì)列:就緒隊(duì)列和阻塞隊(duì)列。
就緒隊(duì)列:存儲(chǔ)將要獲得鎖的線程
阻塞隊(duì)列:存儲(chǔ)被阻塞的的線程
生產(chǎn)者/消費(fèi)者模式
“假死”:線程進(jìn)入WAITING等待狀態(tài),呈假死狀態(tài)的進(jìn)程中所有線程都呈WAITING狀態(tài)。
假死的主要原因:有可能連續(xù)喚醒同類。notify喚醒的不一定是異類,也許是同類,如“生產(chǎn)者”喚醒“生產(chǎn)者”。
解決假死:將notify()改為notifyAll()
wait條件改變,可能出現(xiàn)異常,需要將if改成while
通過管道進(jìn)行線程間通信:一個(gè)線程發(fā)送數(shù)據(jù)到輸出管道,另一個(gè)線程從輸入管道讀數(shù)據(jù)。
字節(jié)流:PipedInputStream和PipedOutputStream
字符流:PipedReader和PipedWriter
join():等待線程對(duì)象銷毀,具有使線程排隊(duì)運(yùn)行的作用。
join()與interrupt()方法彼此遇到會(huì)出現(xiàn)異常。
join(long)可設(shè)定等待的時(shí)間
join與synchronized的區(qū)別:join在內(nèi)部使用wait()方法進(jìn)行等待;synchronized使用的是“對(duì)象監(jiān)視器”原理作為同步
join(long)與sleep(long)的區(qū)別:join(long)內(nèi)部使用wait(long)實(shí)現(xiàn),所以join(long)具有釋放鎖的特點(diǎn);Thread.sleep(long)不釋放鎖。
ThreadLocal類:每個(gè)線程綁定自己的值
覆寫該類的initialValue()方法可以使變量初始化,從而解決get()返回null的問題
InheritableThreadLocal類可在子線程中取得父線程繼承下來的值。
Lock的使用
ReentrantLock類:實(shí)現(xiàn)線程之間的同步互斥,比synchronized更靈活
lock(),調(diào)用了的線程就持有了“對(duì)象監(jiān)視器”,效果和synchronized一樣
使用Condition實(shí)現(xiàn)等待/通知:比wait()和notify()/notyfyAll()更靈活,比如可實(shí)現(xiàn)多路通知。
調(diào)用condition.await()前須先調(diào)用lock.lock()獲得同步監(jiān)視器
Object與Condition方法對(duì)比
Object | Condition |
---|---|
wait() | await() |
wait(long timeout) | await(long time,TimeUnit unit) |
notify() | signal() |
notifyAll() | signalAll() |
一些API
方法 | 說明 |
---|---|
int getHoldCount() | 查詢當(dāng)前線程保持此鎖定的個(gè)數(shù),即調(diào)用lock()方法的次數(shù) |
int getQueueLength() | 返回正在等待獲取此鎖定的線程估計(jì)數(shù) |
int getWaitQueueLength(Condition condition) | 返回等待與此鎖定相關(guān)的給定條件Conditon的線程估計(jì)數(shù) |
boolean hasQueueThread(Thread thread) | 查詢指定的線程是否正在等待獲取此鎖定 |
boolean hasQueueThreads() | 查詢是否有線程正在等待獲取此鎖定 |
boolean hasWaiters(Condition) | 查詢是否有線程正在等待與此鎖定有關(guān)的condition條件 |
boolean isFair() | 判斷是不是公平鎖 |
boolean isHeldByCurrentThread() | 查詢當(dāng)前線程是否保持此鎖定 |
boolean isLocked() | 查詢此鎖定是否由任意線程保持 |
void lockInterruptibly() | 如果當(dāng)前線程未被中斷,則獲取鎖定,如果已經(jīng)被中斷則出現(xiàn)異常 |
boolean tryLock() | 僅在調(diào)用時(shí)鎖定未被另一個(gè)線程保持的情況下,才獲取該鎖定 |
boolean tryLock(long timeout,TimeUnit unit) | 如果鎖定在給定等待時(shí)間內(nèi)沒有被另一個(gè)線程保持,且當(dāng)前線程未被中斷,則獲取該鎖定 |
公平鎖與非公平鎖
公平鎖表示線程獲取鎖的順序是按照加鎖的順序來分配的,即FIFO先進(jìn)先出。
非公平鎖是一種獲取鎖的搶占機(jī)制,隨機(jī)獲得鎖。
ReentrantReadWriteLock類
讀讀共享
寫寫互斥
讀寫互斥
寫讀互斥
定時(shí)器常用API
方法 | 說明 |
---|---|
schedule(TimerTask task, Date time) | 在指定的日期執(zhí)行某一次任務(wù) |
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 在指定的日期之后按指定的間隔周期,無限循環(huán)的執(zhí)行某一任務(wù) |
schedule(TimerTask task, long delay) | 以執(zhí)行此方法的當(dāng)前時(shí)間為參考時(shí)間,在此時(shí)間基礎(chǔ)上延遲指定的毫秒數(shù)后執(zhí)行一次TimerTask任務(wù) |
schedule(TimerTask task, long delay, long period) | 以執(zhí)行此方法的當(dāng)前時(shí)間為參考時(shí)間,在此時(shí)間基礎(chǔ)上延遲指定的毫秒數(shù),再以某一間隔時(shí)間無限次數(shù)地執(zhí)行某一TimerTask任務(wù) |
schedule和scheduleAtFixedRate的區(qū)別:schedule不具有追趕執(zhí)行性;scheduleAtFixedRate具有追趕執(zhí)行性
單例模式與多線程立即加載/“餓漢模式”:調(diào)用方法前,實(shí)例已經(jīng)被創(chuàng)建了。通過靜態(tài)屬性new實(shí)例化實(shí)現(xiàn)的
延遲加載/“懶漢模式”:調(diào)用get()方法時(shí)實(shí)例才被創(chuàng)建。最常見的實(shí)現(xiàn)辦法是在get()方法中進(jìn)行new實(shí)例化
缺點(diǎn):多線程環(huán)境中,會(huì)出問題
解決方法
聲明synchronized關(guān)鍵字,但運(yùn)行效率非常低下
同步代碼塊,效率也低
針對(duì)某些重要代碼(實(shí)例化語句)多帶帶同步,效率提升,但會(huì)出問題
使用DCL雙檢查鎖
使用enum枚舉數(shù)據(jù)類型實(shí)現(xiàn)單例模式
拾遺補(bǔ)增方法與狀態(tài)關(guān)系示意圖
線程的狀態(tài):Thread.State枚舉類,參考官網(wǎng)APIEnum Thread.State
線程組:線程組中可以有線程對(duì)象,也可以有線程組,組中還可以有線程??膳抗芾砭€程或線程組對(duì)象。
SimpleDateFormat非線程安全,解決辦法有:
創(chuàng)建多個(gè)SimpleDateFormat類的實(shí)例
使用ThreadLocal類
線程組出現(xiàn)異常的處理
setUncaughtExceptionHandler()給指定線程對(duì)象設(shè)置異常處理器
setDefaultUncaughtExceptionHandler()對(duì)所有線程對(duì)象設(shè)置異常處理器
參考資料淺談Java中的鎖
java synchronized關(guān)鍵字的用法
Java Thread(線程)案例詳解sleep和wait的區(qū)別
作者@brianway更多文章:個(gè)人網(wǎng)站 | CSDN | oschina
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71133.html
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 1351·2023-04-25 23:47
閱讀 929·2021-11-23 09:51
閱讀 4480·2021-09-26 10:17
閱讀 3729·2021-09-10 11:19
閱讀 3268·2021-09-06 15:10
閱讀 3556·2019-08-30 12:49
閱讀 2436·2019-08-29 13:20
閱讀 1743·2019-08-28 18:14