Thread詳解

具體可參考:Java併發編程:Thread類的使用,這裏對線程狀態的轉換及主要函數做一下補充。

一. 線程狀態轉換圖

  注意:

  1. 調用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開始已經不推薦使用了。

 

作者:水巖
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章