Java多線程系列之二 ------------ 常見API,優化的中斷線程,線程的幾種狀態

         在學習了第一節之後,我們來看一下Thread常見api的學習。

static methods

 

以下方法基本上看源碼代碼,以及翻譯一下源碼的英文註釋 。

1  sleep()方法,

使當前正在運行的線程睡眠多少毫秒,這取決於你的系統的時間定時器和調度器,這個線程
不會失去任何監視器的所有權 (線程會阻塞,但是不會釋放資源和鎖)
/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds, subject to
 * the precision and accuracy of system timers and schedulers. The thread
 * does not lose ownership of any monitors.
 * @param  millis 
           這個參數是以毫秒爲級別的
 *         the length of time to sleep in milliseconds
 * @throws  IllegalArgumentException
             如果是負數就會拋異常 
 *          if the value of {@code millis} is negative
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;

 
  /**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds plus the specified
 * number of nanoseconds, subject to the precision and accuracy of system
 * timers and schedulers. The thread does not lose ownership of any
 * monitors.
 * @param  millis
 *         the length of time to sleep in milliseconds
 * @param  nanos
           如果不是[0,999999] 裏面就會拋出異常
 *         {@code 0-999999} additional nanoseconds to sleep
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative, or the value of
 *          {@code nanos} is not in the range {@code 0-999999}
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
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");
    }
    其實也就是多加入了一個納秒級別的,但是並不是加了就生效還是和nanos>=500000
    一樣
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    sleep(millis);
} 

    2  yield()方法 

/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 */
 通知這個調度器,讓這個正在運行的當前線程去讓出當前的處理器
 也就是說使當前線程從執行狀態(運行狀態)變爲可執行態(就緒狀態)。
 cpu會從衆多的可執行態裏選擇,就是剛剛的那個線程還是有可能會被再次執行到的,
 並不是說一定會執行其他線程而該線程在下一次中不會執行到了。
 public static native void yield();

   3  interrupted()方法  英文註釋太長了,樓主直接給翻譯過來註釋上去了

/**
 測試當前線程是否已被中斷,此方法會清楚線程的中斷狀態。換句話說,如果連續兩次調用
 此方法,則第二次調用將返回false(除非當前線程在第一次調用已清除其中斷狀態之後且在
 第二次調用檢查之前再次中斷)
*/
public static boolean interrupted() {
        return currentThread().isInterrupted(true);
}
作用是測試當前線程是否被中斷(檢查中斷標誌),返回一個boolean並清除中斷狀態,
第二次再調用時中斷狀態已經被清除,將返回一個false。


Instance Methods

   這裏源碼裏面有參考另外兩個實例方法   public void  interrupt()  以及  public boolean isInterrupted(),一起講述一個新知識:

   如何優雅的停止一個線程?首先來看一下interrupt()方法

public void interrupt() {
  if (this != Thread.currentThread())
    checkAccess();
synchronized (blockerLock) {
    Interruptible b = blocker;
    if (b != null) {
        interrupt0();           // Just to set the interrupt flag
        b.interrupt(this);
        return;
    }
}
interrupt0();}

   具體的英文註釋樓主大概解釋一下,Java線程裏面都是協作形式的,協作式的意思就是一個線程不會立馬要另外一個線程停止,例如 A線程中 調用了 B.interrupt()方法,白話來說就是A線程現在給你B線程打了一個招呼你要停止了,至於你B線程停止與否還是看你B線程自己。那麼interrupt()方法具體有哪些用處呢?,翻譯源碼可知

中斷調用該方法的線程,除非當前線程正在中斷自己,否則checkAccess方法有可能拋出SecurityException異常。

(1)如果當前線程由於調用Object的wait(),或者Thread的join和sleep方法阻塞,則退出阻塞且中斷狀態將被清除,並且拋出InterrruptedException異常,(同時中斷狀態爲false),這樣,我們就可以捕捉到中斷異常,並根據實際情況對該線程從阻塞方法中異常退出而進行一些處理。

(2)如果線程由於InterruptibleChannel的IO操作阻塞,則通道將關閉,線程設置中斷狀態,並且線程收到一個ClosedInterruptException異常。

(3)如果線程由於Selector阻塞,線程將處於中斷狀態,並且從selection操作立刻返回,可能是一個非零值。除此之外,線程將被設置中斷狀態。

   總結來說 :interrupt方法有兩個作用:(1)將線程的中斷狀態設置爲true(默認爲false),調用這個方法並不是一個會把線程停止,只是把中斷狀態設置爲true,至於停止與否還需要該線程自己來處理這個中斷狀態 (2)同上面描述的第一個紅字作用。

再看一下isInterrupted() 方法  public boolean isInterrupted() 調用

檢測調用該方法的線程是否被中斷線程一旦被中斷,該方法返回true中斷狀態不會被清除。下面用代碼測試一下如何停止線程

static class StopThread extends Thread{

        @Override
        public void run() {
            //當該線程調用interrupt()方法之後會把中斷狀態改爲true
            while (!interrupted()){
                //默認中斷狀態爲false,如果線程一直alive 就會一直運行while循環體內容
                System.out.println("Thread name = "+getName());
            }
            System.out.println("Thread is stop");
        }
    }

public static void main(String[] args) {
        StopThread thread = new StopThread();
        thread.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();

}

 如果改成下面代碼,就會走入異常,並且把中斷狀態重新設置爲false

 

 

 另外 stop ()  停止線程這個方法已經不建議使用,因爲太過暴力和絕對,直接死掉不會釋放相關資源。

 suspend()使線程掛起不會釋放類似鎖這樣的資源 resume()使線程恢復如果之前沒有使用suspend暫停線程則不起作用,那麼suspend不釋放資源,如果一條線程將去resume目標線程之前嘗試持有這個重要的系統資源再去resume目標線程,那麼就和suspend掛起的線程死鎖了,所以這三個方法不建議使用了 !!!

再來一個判斷是否線程存活的方法

  

  最後一個常見的Join方法 

/**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
*/
    public final void join() throws InterruptedException {
        join(0);
    }

/**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
*/
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
}
如果一個線程A調用了 B線程的join()方法,則讓出CPU的執行權,讓B線程執行完畢之後 A才接着執行。
t.join(); //使調用線程 t 在此之前執行完畢。
t.join(1000);  //等待 t 線程,等待時間是1000毫秒。Join方法實現是通過wait(小提示:Object 提供的方法)。 當main線程調用t.join時候,main線程會獲得線程對象t的鎖(wait 意味着拿到該對象的鎖),調用該對象的wait(等待時間),直到該對象喚醒main線程 ,比如退出後。這就意味着main 線程調用t.join時,必須能夠拿到線程t對象的鎖,下面看個例子 
public class JoinTest {

    static class JumpQueue implements Runnable{
        private Thread thread;
        JumpQueue(Thread thread){
            this.thread = thread;
        }
        @Override
        public void run() {
            try {
                //此時構造初始完畢 thread 傳入進來是主線程main 則main執行完畢之後當先子線程0才能拿到返回對象 0 繼續執行,否則一直等待main執行
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" terminate subclass");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread previous = Thread.currentThread();
        //首先第一步獲取得到的previous是主線程
        for(int i= 0;i<10;i++){
            Thread thread = new Thread(new JumpQueue(previous),String.valueOf(i));
            //第一次得到的是子線程 thread [0 ,5,main] 線程主是main線程
            System.out.println(previous.getName()+" jump a queue the thread :"+thread.getName());
            //所以第一次打印 main  jump a queue the thread : 0
            thread.start();
            previous = thread;
        }
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName()+" terminate class");
    }
}

  基本上Thread類常用API已經分析了,Thread類裏面有個枚舉類                                                                                                     public enum State { NEW, RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED }

   NEW(新建/初始狀態):    新建的一個Thread,還沒有調用start()方法

   RUNNABLE (可運行狀態):    調用了start()方法,變成可運行的線程狀態,但是否運行還是取決於操作系統和資源的調度

   BLOCKED (阻塞狀態):    比如線程裏面調用 t.join()  Thread.sleep() 等

   WAITING/TIME_WAITING(等待狀態) :  具有指定等待時間的等待線程的hread狀態

   TERMINATED(結束狀態):線程運行完了/異常結束都算結束狀態。

總結 :

    wait()、notify()/notifyAll()、sleep()、yield()、join()  的理解

1)sleep()是Thread的靜態方法,不用在同步環境使用,作用當前線程,不釋放鎖

2)yield()是Thread的靜態方法,作用當前線程,釋放當前線程持有的CPU資源,將CPU讓給優先級不低於自己的線程用,調用後進入就緒狀態,不釋放鎖

3)join()是Thread的實例方法,作用是阻塞當前線程,讓新加入的線程運行,由於join()裏面源碼還是調用的wait()方法

下面重點說一下 wait,notify,nitifyAll 

 1、wait()、notify/notifyAll() 方法是Object的本地final方法,無法被重寫。

 2、wait()使當前線程阻塞,前提是 必須先獲得鎖,一般配合synchronized 關鍵字使用,即,一般在synchronized 同步代碼塊裏使用 wait()、notify/notifyAll() 方法。

3、 由於 wait()、notify/notifyAll() 在synchronized 代碼塊執行,說明當前線程一定是獲取了鎖的。當線程執行wait()方法時候,會釋放當前的鎖,然後讓出CPU,進入等待狀態。只有當 notify/notifyAll() 被執行時候,纔會喚醒一個或多個正處於等待狀態的線程,然後繼續往下執行,直到執行完synchronized 代碼塊的代碼或是中途遇到wait() ,再次釋放鎖。也就是說,notify/notifyAll() 的執行只是喚醒沉睡的線程,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執行情況。所以在編程中,儘量在使用了notify/notifyAll() 後立即退出臨界區,以喚醒其他線程讓其獲得鎖。

4、notify 和wait 的順序不能錯,如果A線程先執行notify方法,B線程在執行wait方法,那麼B線程是無法被喚醒的。

5、notify 和 notifyAll的區別  notify方法只喚醒一個等待(對象的)線程並使該線程開始執行。所以如果有多個線程等待一個對象,這個方法只會喚醒其中一個線程,選擇哪個線程取決於操作系統對多線程管理的實現。notifyAll 會喚醒所有等待(對象的)線程,儘管哪一個線程將會第一個處理取決於操作系統的實現。如果當前情況下有多個線程需要被喚醒,推薦使用notifyAll 方法。比如在生產者-消費者裏面的使用,每次都需要喚醒所有的消費者或是生產者,以判斷程序是否可以繼續往下執行。

6、在多線程中要測試某個條件的變化,使用if 還是while?

  要注意,notify喚醒沉睡的線程後,線程會接着上次的執行繼續往下執行。所以在進行條件判斷時候,可以先把 wait 語句忽略不計來進行考慮;顯然,要確保程序一定要執行,並且要保證程序直到滿足一定的條件再執行,要使用while進行等待,直到滿足條件才繼續往下執行。

 

 

 

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