具體可參考:Java併發編程:Thread類的使用,這裏對線程狀態的轉換及主要函數做一下補充。
一. 線程狀態轉換圖
注意:
- 調用obj.wait()的線程需要先獲取obj的monitor,wait()會釋放obj的monitor並進入等待態。所以wait()/notify()都要與synchronized聯用。詳見:JAVA多線程之wait/notify
1.1 阻塞與等待的區別
阻塞:當一個線程試圖獲取對象鎖(非java.util.concurrent庫中的鎖,即synchronized),而該鎖被其他線程持有,則該線程進入阻塞狀態。它的特點是使用簡單,由JVM調度器來決定喚醒自己,而不需要由另一個線程來顯式喚醒自己,不響應中斷。
等待:當一個線程等待另一個線程通知調度器一個條件時,該線程進入等待狀態。它的特點是需要等待另一個線程顯式地喚醒自己,實現靈活,語義更豐富,可響應中斷。例如調用:Object.wait()、Thread.join()以及等待Lock或Condition。
需要強調的是雖然synchronized和JUC裏的Lock都實現鎖的功能,但線程進入的狀態是不一樣的。synchronized會讓線程進入阻塞態,而JUC裏的Lock是用LockSupport.park()/unpark()來實現阻塞/喚醒的,會讓線程進入等待態。但話又說回來,雖然等鎖時進入的狀態不一樣,但被喚醒後又都進入runnable態,從行爲效果來看又是一樣的。
二. 主要操作
2.1 start()
新啓一個線程執行其run()方法,一個線程只能start一次。主要是通過調用native start0()來實現。
1 public synchronized void start() { 2 //判斷是否首次啓動 3 if (threadStatus != 0) 4 throw new IllegalThreadStateException(); 5 6 group.add(this); 7 8 boolean started = false; 9 try { 10 //啓動線程 11 start0(); 12 started = true; 13 } finally { 14 try { 15 if (!started) { 16 group.threadStartFailed(this); 17 } 18 } catch (Throwable ignore) { 19 /* do nothing. If start0 threw a Throwable then 20 it will be passed up the call stack */ 21 } 22 } 23 } 24 25 private native void start0();
2.2 run()
run()方法是不需要用戶來調用的,當通過start方法啓動一個線程之後,當該線程獲得了CPU執行時間,便進入run方法體去執行具體的任務。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務。
2.3 sleep()
sleep方法有兩個重載版本:
1 sleep(long millis) //參數爲毫秒 2 3 sleep(long millis,int nanoseconds) //第一參數爲毫秒,第二個參數爲納秒
sleep相當於讓線程睡眠,交出CPU,讓CPU去執行其他的任務。
但是有一點要非常注意,sleep方法不會釋放鎖,也就是說如果當前線程持有對某個對象的鎖,則即使調用sleep方法,其他線程也無法訪問這個對象。
2.4 yield()
調用yield方法會讓當前線程交出CPU權限,讓CPU去執行其他的線程。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先級的線程有獲取CPU執行時間的機會。
注意,調用yield方法並不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。
2.5 join()
join方法有三個重載版本:
1 join() 2 join(long millis) //參數爲毫秒 3 join(long millis,int nanoseconds) //第一參數爲毫秒,第二個參數爲納秒
join()實際是利用了wait(),只不過它不用等待notify()/notifyAll(),且不受其影響。它結束的條件是:1)等待時間到;2)目標線程已經run完(通過isAlive()來判斷)。
1 public final synchronized void join(long millis) throws InterruptedException { 2 long base = System.currentTimeMillis(); 3 long now = 0; 4 5 if (millis < 0) { 6 throw new IllegalArgumentException("timeout value is negative"); 7 } 8 9 //0則需要一直等到目標線程run完 10 if (millis == 0) { 11 while (isAlive()) { 12 wait(0); 13 } 14 } else { 15 //如果目標線程未run完且阻塞時間未到,那麼調用線程會一直等待。 16 while (isAlive()) { 17 long delay = millis - now; 18 if (delay <= 0) { 19 break; 20 } 21 wait(delay); 22 now = System.currentTimeMillis() - base; 23 } 24 } 25 }
2.6 interrupt()
此操作會中斷等待中的線程,並將線程的中斷標誌位置位。如果線程在運行態則不會受此影響。
可以通過以下三種方式來判斷中斷:
1)isInterrupted()
此方法只會讀取線程的中斷標誌位,並不會重置。
2)interrupted()
此方法讀取線程的中斷標誌位,並會重置。
3)throw InterruptException
拋出該異常的同時,會重置中斷標誌位。
2.7 suspend()/resume()
掛起線程,直到被resume,纔會甦醒。
但調用suspend()的線程和調用resume()的線程,可能會因爲爭鎖的問題而發生死鎖,所以JDK 7開始已經不推薦使用了。