JAVA多執行緒(七) ReentrantLock原理分析
多執行緒
程序與執行緒
-
一個程序有多個執行緒
-
程序之間相互隔離,執行緒之間可以相互通訊
-
cpu一個時間點只能執行一個執行緒,但多個執行緒之間的切換比較快,給人一種多個執行緒同時執行的錯覺
實現多執行緒的3種方法
-
繼承Thread類(不推薦)
-
建立類,繼承Thread類
-
重寫run()方法
-
建立物件,使用start()方法啟動執行緒
Java有單繼承的侷限性,儘量不使用這種方法
-
-
實現Runnable介面(推薦使用)
-
建立類,實現Runnable介面
-
重寫run()方法
-
建立物件,並將其作為Thread類的構造引數,建立Thread類,呼叫start()方法啟動執行緒
這種方法避免使用繼承,緩解了Java單繼承的侷限性,並且使用靜態代理,可以節約資源
使用到了靜態代理,一個資源(物件),多次代理(Thread類代理、多個執行緒使用一個物件)
-
-
實現Callable介面(投入工作後會使用到,前期不使用)
執行緒的狀態以及操作
狀態
-
準備狀態:當new一個執行緒(new Thread())時,進入準備狀態
-
就緒狀態
-
事件1:執行緒呼叫start()方法,啟動執行緒,進入就緒狀態,開始和其他執行緒搶佔cpu資源
-
-
執行狀態
-
事件2:通過排程演算法,執行緒被cpu排程,獲取到cpu資源,程序執行緒內的邏輯操作,進入執行狀態
-
-
阻塞狀態
-
事件3:執行緒呼叫sleep、wait方法或者同步鎖定時,當前執行緒進入阻塞狀態
-
事件4:阻塞時間結束後,則重新進入準備就緒狀態,開始搶佔cpu資源
-
-
終止(死亡)狀態
-
事件5:執行緒被停止、執行緒邏輯操作執行完畢等事件發生後,執行緒進入終止狀態
-
一旦進入死亡狀態的執行緒將不能被再次啟動
-
操作
-
執行緒停止
-
不建議使用stop()、destory()等方法,容易造成為止的錯誤
-
建議設定一個Boolean值進行控制,要停止執行緒時,就將其切換為false
-
-
執行緒禮讓
-
yield(),執行緒呼叫該方法後進入就緒狀態,重新和其他執行緒搶佔cpu
-
-
執行緒休眠
-
sleep(),執行緒呼叫該方法後進入阻塞狀態,過了設定的時間後,進入繼續狀態,和其他執行緒搶佔cpu
-
-
執行緒強制執行
-
join(),執行緒呼叫該方法後立即進入執行狀態,之前正在執行的執行緒進入阻塞狀態
-
執行緒狀態觀測
-
使用Thread.getState()來獲取該執行緒的狀態
-
執行緒狀態有幾個常量
-
NEW:尚未啟動的執行緒處於此狀態[剛new出來的執行緒]
-
RUNNABLE:在Java虛擬機器中執行的執行緒處於此狀態[呼叫start()方法後的執行緒]
-
BLOCKED:被阻塞等待監視器鎖定的執行緒處於此狀態
-
WAITING:正在等待另一個執行緒執行特定動作的執行緒處於此狀態
-
TIME_WAITING:正在等待另一個執行緒執行動作達到指定等待時間的執行緒處於此狀態[呼叫了sleep的執行緒]
-
TERMINATED:已退出的執行緒處於此狀態
-
執行緒優先順序
-
getPriority、setPriority分別用來獲得和設定執行緒的優先順序
-
執行緒優先順序的範圍在1-10,MIN_PRIORITY=1, MAX_PRIORITY=10, (預設優先順序)NORM_PRIORITY=5
-
設定優先順序要線上程start之前設定,不然無效
-
優先順序高的不一定先執行,只是被執行的概率更高,同時這會引發一個性能倒置的問題
守護執行緒
-
Java虛擬機器只需要保證執行完使用者執行緒(非守護執行緒)
-
setDaemon(true),即可使該執行緒成為守護執行緒,預設為false
執行緒同步
多個執行緒操作同一個資源,會導致一些不安全的事件發生。例如,銀行取錢,兩個人同時取一個銀行卡的錢,兩人取錢的總額大於銀行卡餘額,結果餘額就會出現負數。這就是一種嚴重的錯誤。
所以我們要用到鎖和佇列
當一個執行緒操作一個資源的時候,給他一個鎖,這樣來防止同一時間有其他執行緒來操作這個資源。在使用過資源後,釋放鎖,交由下一個執行緒。也就形成了一個佇列。
加鎖
-
使用synchronized關鍵字
每個物件都有一把鎖
-
對方法加鎖
public synchronized void method(){
邏輯程式碼
}
這樣就實現對方法加鎖,線上程呼叫這個方法的時候,就會預設使用this物件(執行緒物件)來加鎖
-
程式碼塊加鎖
@Override
public void run(){
synchronized (一個物件,一般使用被操作的資源){
邏輯程式碼
}
}
當執行到程式碼塊時,括號裡的物件將會被鎖上,邏輯程式碼執行完之前,其他執行緒不能訪問
-
-
使用Lock(介面)鎖
ReentrantLock類實現了Lock介面
-
使用方法
class testLock implements Runnable{
private final ReentrantLock lock =
newReentrantLock();
@Override
public void run() {
while(true){
try {
lock.lock();//加鎖
邏輯程式碼
}finally {
lock.unlock();//解鎖
}
}
synchronized與Lock的區別,前者是隱性的上鎖開鎖,在進入被其修飾的程式碼時自動上鎖,在跳出程式碼塊後自動解鎖
而Lock則是需要手動的上鎖解鎖,是顯性的
-
死鎖
兩個執行緒在分別已經擁有一個物件鎖並且未解鎖的情況下,互相請求獲取對方的物件鎖,就會造成死鎖
例如:小明拿著玩具1,小紅拿著玩具2,兩人想要對方的玩具的同時,還不想放棄自己的玩具。兩人就會僵持住(死鎖)
生產者和消費者
wait()方法:this.wait,會釋放鎖,所以一般都是使用的同一個物件
notifyAll():通知所有wait的執行緒啟動