概要
目前CPU的運算速度已經達到了百億次每秒,甚至更高的量級,家用電腦即使維持作業系統正常執行的程式也會有數十個,執行緒更是數以百計。
執行緒是CPU的排程和分派的基本單位,為了更充分地利用CPU資源以及提高生產率和高效地完成任務,在現實場景中一般都會採用多執行緒處理。
執行緒的生命週期
執行緒的生命週期大致可以分為下面五種狀態:New(新建狀態)、RUNABLE(就緒狀態)、RUNNING(執行狀態)、休眠狀態、DEAD(終止狀態)
1、新建狀態,是執行緒被建立且未啟動的狀態;這裡的建立,僅僅是在JAVA的這種程式語言層面被建立,而在作業系統層面,真正的執行緒還沒有被建立。
Thread t1 = new Thread()
複製程式碼
2、就緒狀態,指的是呼叫start()方法之後,執行緒等待分配給CPU執行(這時候,執行緒已經在作業系統層面被建立)
t1.start()
複製程式碼
3、執行狀態,當CPU空閒時,執行緒被分得CPU時間片,執行Run()方法的狀態
4、休眠狀態,執行狀態的執行緒,如果呼叫一個阻塞的API或者等待某個事件,那麼執行緒的狀態就會轉換到休眠狀態,一般有以下幾種情況
- 同步阻塞:鎖被其它執行緒佔用
- 主動阻塞:呼叫Thread的某些方法,主動讓出CPU執行權,比如:sleep()、join()等方法
- 等待阻塞:執行了wait()方法
5、終止狀態,執行緒執行完(run()方法執行結束
)或者出現異常就會進入終止狀態
對應的是JAVA中Thread類State
中的六種狀態
public class Thread implements Runnable {
//.........
public enum State {
NEW, // 初始化狀態
RUNNABLE, // 可執行/執行狀態
BLOCKED, // 阻塞狀態
WAITING, // 無時限等待
TIMED_WAITING, // 有時限等待
TERMINATED; // 終止狀態
}
// ..........
}
複製程式碼
休眠狀態(BLOCKED、WAITING、TIMED_WAITING)與RUNNING狀態的轉換
1、RUNNING狀態與BLOCKED狀態的轉換
-
執行緒等待 synchronized 的隱式鎖,RUNNING —> BLOCKED
-
執行緒獲得 synchronized 的隱式鎖,BLOCKED —> RUNNING
2、RUNNING狀態與WAITING狀態的轉換
- 獲得 synchronized 隱式鎖的執行緒,呼叫無引數的Object.wait()方法
- 呼叫無引數Thread.join()方法
- 呼叫LockSupport.park()方法,執行緒阻塞切換到WAITING狀態,
- 呼叫LockSupport.unpark()方法,可喚醒執行緒,從WAITING狀態切換到RUNNING狀態
3、RUNNING狀態與TIMED_WAITING狀態的轉換
- 呼叫帶超時引數的 Thread..sleep(long millis)方法
- 獲得 synchronized 隱式鎖的執行緒,呼叫帶超時引數的Object.wait(long timeout)方法
- 呼叫帶超時引數的Thread.join(long millis)方法
- 呼叫帶超時引數的LockSupport.parkNanos(Object blocker,long deadline)方法
- 呼叫帶超時引數的LockSupport.parkUntil(long deadline)方法
廢棄掉的執行緒方法 :stop()、suspend()、resume()
stop()
方法,會真正的殺死執行緒,不給執行緒任何喘息的機會,假設獲得 synchronized 隱式鎖的執行緒,此刻執行stop()
方法,該鎖不會被釋放,導致其它執行緒沒有任何機會獲得鎖,顯然這樣的結果不是我們想要見到的。
suspend()
和resume()
方法同樣,因為某種不可預料的原因,已經被建議不在使用
不能使用stop()、suspend() 、resume()
這些方法來終止執行緒或者喚醒執行緒,那麼我們應該使用什麼方法來做呢?答案是:優雅的使用Thread.interrupt()
方法來做
優雅的Thread.interrupt()方法中斷執行緒
interrupt()
方法僅僅是通知執行緒,執行緒有機會執行一些後續操作,同時也可以無視這個通知,這個方法通過修改了呼叫執行緒的中斷狀態來告知那個執行緒,說它被中斷了,執行緒可以通過isInterrupted()
方法,檢測是不是自己被中斷。
當執行緒被阻塞的時候,比如被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞時;呼叫它的interrput()
方法,會產生InterruptedException異常。
擴充套件知識點
探祕區域性變數不會引發併發問題的原因
在Java領域,執行緒可以擁有自己的運算元棧,程式計數器、區域性變數表等資源;我們都知道,多個執行緒同時訪問共享變數的時候,會導致資料不一致性等併發問題;但是 Java 方法裡面的區域性變數是不存在併發問題的,具體為什麼呢?我們先來了解一下這些基礎知識。
區域性變數的作用域是方法內部的,當方法執行完了,區域性變數也就銷燬了,也就是說區域性變數應該是和方法同生共死的。
Java中的方法是如何呼叫的?當呼叫方法時,會建立新的棧幀,並壓入呼叫棧;當方法返回時,對應的棧幀就會被自動彈出。也就是說,棧幀和方法是同生共死的。
從上面我們可以得出:方法的呼叫就是壓棧和出棧的過程,而在Java中的方法的區域性變數又是儲存在棧幀中,所以我們用下面的示意圖幫助大家理解一下
說了那麼多,我們還沒有解釋區域性變數為啥不會產生併發問題,以上,我們知道了,方法的呼叫是壓棧和出棧(棧幀)的過程,區域性變數又儲存在棧幀中。那麼我們的執行緒和呼叫棧又有什麼關係呢,答案是:每個執行緒都有自己獨立的呼叫棧
到現在,相信大家都已經明白了,區域性變數之所以不存在併發問題,是因為,每個執行緒都有自己的呼叫棧,區域性變數都儲存線上程各自的呼叫棧裡面,沒有共享,自然就不存在併發問題。
歡迎大家關注公眾號:小白程式之路(whiteontheroad),第一時間獲取最新資訊!!!