線程:Thread類源碼解析(下)

4.Thread API

sleep方法

sleep 方法會使線程休眠指定的時間長度。

休眠的意思是,當前邏輯執行到此不再繼續執行,而是等待指定的時間。但在這段時間內,該線程持有的 monitor 鎖(monitor鎖見synchronized筆記)並不會被放棄。可以認爲線程只是工作到一半休息了一會,但它所佔有的資源並不會交還。

這樣設計是因爲線程在 sleep 的時候可能是處於同步代碼塊的中間位置,如果此時把鎖放棄,就違背了同步的語義。所以 sleep 時並不會放棄鎖,等過了 sleep 時長後,可以確保後面的邏輯還在同步執行。

sleep方法有兩個重載,

public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException

兩者的區別只是一個支持休眠時間到毫秒級,另外一個到納秒級。但其實第二個並不能真的精確到納秒級別。第二個重載方法代碼,

public static void sleep(long millis, int nanos)
throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}

可以清楚的看到,最終調用的還是第一個毫秒級別的 sleep 方法。而傳入的納秒會被四捨五入。如果大於 50 萬(0.5毫秒),毫秒++,否則納秒被省略。

yield方法

public static native void yield();

該方法是一個native方法,意思是當前線程做出讓步,放棄當前 cpu使用權,讓 cpu 重新選擇線程,避免線程過度使用 cpu。

在寫 while 死循環的時候,預計短時間內 while 死循環可以結束的話,可以在循環裏面使用 yield 方法,防止 cpu 一直被 while 死循環霸佔。

有點需要說明的是,讓步不是絕不執行,重新競爭時,cpu 也有可能重新選中自己。

setPriority方法

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

Thread 有的最小和最大優先級數值,範圍在 1-10。如果不在此範圍內,則會報錯。

如果設置的 priority 超過了線程所在組的 priority ,那麼只能被設置爲組的最高 priority 。最後通過調用 native 方法 setPriority0 進行設置。

interrupt方法*

一個線程在未正常結束之前, 被強制終止是很危險的事情,可能帶來完全預料不到的嚴重後果。比如持有鎖的線程被強制終止後未進行釋放使得其他線程始終無法訪問資源等。 所以在Thread類的源代碼中Thread.suspend, Thread.stopThread.resume等方法都被標註爲Deprecated。

在多線程開發中會遇到如下需求,

  1. 有必要讓一個線程死掉
  2. 結束線程的某種等待的狀態

直接終止線程是不可行的,安全且間接實現上面兩個需求的方法有兩種,

  • 使用等待/通知機制,見synchronized筆記
  • 給線程一箇中斷信號, 讓它自己決定該怎麼辦

被調用interrupt方法的線程,其中斷狀態會被修改。執行過程取決於線程在被調用interrupt方法時的狀態,如,

  1. 對於一個非阻塞狀態的線程而言,interrupt方法只改變中斷狀態,線程被調用該方法後返回 true
  2. 若調用interrupt方法之前,線程調用了Object#wait()Thread#join()Thread#sleep(long) 這些方法處於 WAITING 或 TIMED_WAITING狀態。此時對線程調用interrupt方法,就會拋出 InterruptedException 異常,拋出異常的原因是沒有佔用CPU(waitjoin方法)或佔用CPU缺不運行(sleep方法)的線程是不可能給自己的中斷狀態置位的
  3. 不是所有的阻塞方法收到中斷後都可以取消阻塞狀態,如果 I/O 操作被阻塞了,主動打斷當前線程,會拋出 ClosedByInterruptException 異常,且在被中斷的情況下也不會退出阻塞狀態

故interrupt 方法的作用是讓可中斷的方法中斷(如sleep方法)。中斷的並不是線程的邏輯,中斷的是線程的阻塞。這一點要徹底搞清。

1)中斷非阻塞線程

public class InterruptClient {
    public void run(){  
        while(true){  
            if(Thread.currentThread().isInterrupted()){  
                System.out.println("Someone interrupted me.");  
            }  
            else{  
                System.out.println("Thread is Going...");  
            }
        }  
    }  

    public static void main(String[] args) throws InterruptedException {  
        InterruptClient t = new InterruptClient ();  
        t.start();  
        Thread.sleep(3000);  
        t.interrupt();  
    }  
}

上面的代碼執行後,

  • 在主線程sleep的過程中由於子線程線程t中isInterrupted()返回結果爲false,所以不斷的輸出 “Thread is going”
  • 當子線程的interrupt()方法被調用後,t線程中isInterrupted()返回結果變爲爲true。此時會輸出 “Someone interrupted me.” 。但是線程並不會因爲中斷信號而停止運行。因爲它只是被修改一箇中斷信號而已

明確兩個概念,

  1. 調用 interrupt 方法,並不會影響可非阻塞狀態的線程。非阻塞狀態的線程不會停止,會繼續執行。interrupt中斷的是線程的某一部分業務邏輯,前提是線程需要檢查自己的中斷狀態
  2. 一旦調用了 interrupt 方法,那麼線程的 interrupted 狀態會一直爲 ture(沒有通過調用可中斷方法或者其他方式主動清除標識的情況下)

2)中斷阻塞線程

public class InterruptSleepClient {
    public static void main(String[] args) throws InterruptedException {
        Thread xiaopang = new Thread(()->{
            for(int i=0; i<100 ;i++){
                System.out.println("I'm doing my work");
                try {
                    System.out.println("I will sleep");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    System.out.println("My sleeping was interrupted");
                }
                System.out.println("I'm interrupted?"+Thread.currentThread().isInterrupted());
            }
        });
        xiaopang.start();
        Thread.sleep(1);
        xiaopang.interrupt();
    }
}

執行結果,

I’m doing my work
I will sleep
My sleeping was interrupted
I’m interrupted? false
I’m doing my work
I will sleep
I’m interrupted? false
I’m doing my work
I will sleep
I’m interrupted? false

可以看到當 xiaopang.interrupt () 執行後,睡眠中的 xiaopang 被喚醒了。

這裏額外需要注意的是,此時 xiaopang 線程的 interrupted 狀態還是 false 。因爲可中斷線程會捕獲中斷的信號,並且會清除掉 interrupted 標識。因此輸出的 “I’m interrupted ?” 全部是 false 。

Join方法

join 的意思就是當前線程等待另一個線程執行完成之後,才能繼續操作。

public void join() throws Exception {
  Thread main = Thread.currentThread();
  log.info("{} is run。",main.getName());
  
  Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
      log.info("{} begin run",Thread.currentThread().getName());
      try {
        Thread.sleep(30000L);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      log.info("{} end run",Thread.currentThread().getName());
    }
  });
  // 開一個子線程去執行
  thread.start();
  // 當前主線程等待子線程執行完成之後再執行
  thread.join();
  log.info("{} is end", Thread.currentThread());
}

執行的結果,就是主線程在執行 thread.join (); 代碼後會停住,子線程沉睡 30 秒後執行完畢,此後再執行主線程。這裏的 join 的作用就是讓主線程等待子線程執行完成。整個過程示意圖如下,
image
從圖中可以看出,主線程一直等待子線程執行完畢後才執行。在等待期間,主線程的狀態也是 TIMED_WAITING

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