摘要:本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,的使用,定時器,單例模式,以及線程狀態與線程組。源碼采用構建,多線程這部分源碼位于模塊中。通知可能等待該對象的對象鎖的其他線程。
本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,lock的使用,定時器,單例模式,以及線程狀態與線程組。
寫在前面花了一周時間閱讀《java多線程編程核心技術》(高洪巖 著),本文算是此書的整理歸納,書中幾乎所有示例,我都親手敲了一遍,并上傳到了我的github上,有興趣的朋友可以到我的github下載。源碼采用maven構建,多線程這部分源碼位于java-multithread模塊中。
java多線程倉庫地址:java-learning
git clone: git@github.com:brianway/java-learning.git
基礎知識
創建線程的兩種方式: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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71133.html
摘要:哪吒社區技能樹打卡打卡貼函數式接口簡介領域優質創作者哪吒公眾號作者架構師奮斗者掃描主頁左側二維碼,加入群聊,一起學習一起進步歡迎點贊收藏留言前情提要無意間聽到領導們的談話,現在公司的現狀是碼農太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區Java技能樹打卡?【打卡貼 day2...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 1336·2023-04-25 23:47
閱讀 912·2021-11-23 09:51
閱讀 4432·2021-09-26 10:17
閱讀 3706·2021-09-10 11:19
閱讀 3254·2021-09-06 15:10
閱讀 3546·2019-08-30 12:49
閱讀 2421·2019-08-29 13:20
閱讀 1730·2019-08-28 18:14