1. 程式人生 > >java執行緒及狀態轉換

java執行緒及狀態轉換

執行緒狀態圖

說明: 執行緒共包括以下5種狀態。 1. 新建狀態(New)         : 執行緒物件被建立後,就進入了新建狀態。例如,Thread thread = new Thread()。 2. 就緒狀態(Runnable): 也被稱為“可執行狀態”。執行緒物件被建立後,其它執行緒呼叫了該物件的start()方法,從而來啟動該執行緒。例如,thread.start()。處於就緒狀態的執行緒,隨時可能被CPU排程執行。 3. 執行狀態(Running) : 執行緒獲取CPU許可權進行執行。需要注意的是,執行緒只能從就緒狀態進入到執行狀態。 4. 阻塞狀態(Blocked)  : 阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。阻塞的情況分三種:     (01) 等待阻塞 -- 通過呼叫執行緒的wait()方法,讓執行緒等待某工作的完成。     (02) 同步阻塞 -- 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態。     (03) 其他阻塞 -- 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。 5. 死亡狀態(Dead)

    : 執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

1. wait(), notify(), notifyAll()等方法介紹

在Object.java中,定義了wait(), notify()和notifyAll()等介面。wait()的作用是讓當前執行緒進入等待狀態,同時,wait()也會讓當前執行緒釋放它所持有的鎖。而notify()和notifyAll()的作用,則是喚醒當前物件上的等待執行緒;notify()是喚醒單個執行緒,而notifyAll()是喚醒所有的執行緒。

Object類中關於等待/喚醒的API詳細資訊如下:notify()        

-- 喚醒在此物件監視器上等待的單個執行緒。notifyAll()   -- 喚醒在此物件監視器上等待的所有執行緒。wait()                                         -- 讓當前執行緒處於“等待(阻塞)狀態”,“直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法”,當前執行緒被喚醒(進入“就緒狀態”)。wait(long timeout)                    -- 讓當前執行緒處於“等待(阻塞)狀態”,“直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前執行緒被喚醒(進入“就緒狀態”)。wait(long timeout, int nanos)  
-- 讓當前執行緒處於“等待(阻塞)狀態”,“直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法,或者其他某個執行緒中斷當前執行緒,或者已超過某個實際時間量”,當前執行緒被喚醒(進入“就緒狀態”)。

2. 為什麼notify(), wait()等函式定義在Object中,而不是Thread中

Object中的wait(), notify()等函式,和synchronized一樣,會對“物件的同步鎖”進行操作。

wait()會使“當前執行緒”等待,因為執行緒進入等待狀態,所以執行緒應該釋放它鎖持有的“同步鎖”,否則其它執行緒獲取不到該“同步鎖”而無法執行! OK,執行緒呼叫wait()之後,會釋放它鎖持有的“同步鎖”;而且,根據前面的介紹,我們知道:等待執行緒可以被notify()或notifyAll()喚醒。現在,請思考一個問題:notify()是依據什麼喚醒等待執行緒的?或者說,wait()等待執行緒和notify()之間是通過什麼關聯起來的?答案是:依據“物件的同步鎖”。

負責喚醒等待執行緒的那個執行緒(我們稱為“喚醒執行緒”),它只有在獲取“該物件的同步鎖”(這裡的同步鎖必須和等待執行緒的同步鎖是同一個),並且呼叫notify()或notifyAll()方法之後,才能喚醒等待執行緒。雖然,等待執行緒被喚醒;但是,它不能立刻執行,因為喚醒執行緒還持有“該物件的同步鎖”。必須等到喚醒執行緒釋放了“物件的同步鎖”之後,等待執行緒才能獲取到“物件的同步鎖”進而繼續執行。

總之,notify(), wait()依賴於“同步鎖”,而“同步鎖”是物件鎖持有,並且每個物件有且僅有一個!這就是為什麼notify(), wait()等函式定義在Object類,而不是Thread類中的原因。

 3. yield()介紹

yield()的作用是讓步。它能讓當前執行緒由“執行狀態”進入到“就緒狀態”,從而讓其它具有相同優先順序的等待執行緒獲取執行權;但是,並不能保證在當前執行緒呼叫yield()之後,其它具有相同優先順序的執行緒就一定能獲得執行權;也有可能是當前執行緒又進入到“執行狀態”繼續執行!

4. yield() 與 wait()的比較

我們知道,wait()的作用是讓當前執行緒由“執行狀態”進入“等待(阻塞)狀態”的同時,也會釋放同步鎖。而yield()的作用是讓步,它也會讓當前執行緒離開“執行狀態”。它們的區別是: (01) wait()是讓執行緒由“執行狀態”進入到“等待(阻塞)狀態”,而不yield()是讓執行緒由“執行狀態”進入到“就緒狀態”。 (02) wait()是會執行緒釋放它所持有物件的同步鎖,而yield()方法不會釋放鎖。

複製程式碼

// YieldLockTest.java 的原始碼
public class YieldLockTest{ 

    private static Object obj = new Object();

    public static void main(String[] args){ 
        ThreadA t1 = new ThreadA("t1"); 
        ThreadA t2 = new ThreadA("t2"); 
        t1.start(); 
        t2.start();
    } 

    static class ThreadA extends Thread{
        public ThreadA(String name){ 
            super(name); 
        } 
        public void run(){ 
            // 獲取obj物件的同步鎖
            synchronized (obj) {
                for(int i=0; i <10; i++){ 
                    System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i); 
                    // i整除4時,呼叫yield
                    if (i%4 == 0)
                        Thread.yield();
                }
            }
        } 
    } 
}

複製程式碼

(某一次)執行結果

複製程式碼

t1 [5]:0
t1 [5]:1
t1 [5]:2
t1 [5]:3
t1 [5]:4
t1 [5]:5
t1 [5]:6
t1 [5]:7
t1 [5]:8
t1 [5]:9
t2 [5]:0
t2 [5]:1
t2 [5]:2
t2 [5]:3
t2 [5]:4
t2 [5]:5
t2 [5]:6
t2 [5]:7
t2 [5]:8
t2 [5]:9

複製程式碼

結果說明: 主執行緒main中啟動了兩個執行緒t1和t2。t1和t2在run()會引用同一個物件的同步鎖,即synchronized(obj)。在t1執行過程中,雖然它會呼叫Thread.yield();但是,t2是不會獲取cpu執行權的。因為,t1並沒有釋放“obj所持有的同步鎖”!

5. sleep()介紹

sleep() 定義在Thread.java中。 sleep() 的作用是讓當前執行緒休眠,即當前執行緒會從“執行狀態”進入到“休眠(阻塞)狀態”。sleep()會指定休眠時間,執行緒休眠的時間會大於/等於該休眠時間;線上程重新被喚醒時,它會由“阻塞狀態”變成“就緒狀態”,從而等待cpu的排程執行。

6. sleep() 與 wait()的比較

我們知道,wait()的作用是讓當前執行緒由“執行狀態”進入“等待(阻塞)狀態”的同時,也會釋放同步鎖。而sleep()的作用是也是讓當前執行緒由“執行狀態”進入到“休眠(阻塞)狀態”。 但是,wait()會釋放物件的同步鎖,而sleep()則不會釋放鎖。 下面通過示例演示sleep()是不會釋放鎖的。

複製程式碼

// SleepLockTest.java的原始碼
public class SleepLockTest{ 

    private static Object obj = new Object();

    public static void main(String[] args){ 
        ThreadA t1 = new ThreadA("t1"); 
        ThreadA t2 = new ThreadA("t2"); 
        t1.start(); 
        t2.start();
    } 

    static class ThreadA extends Thread{
        public ThreadA(String name){ 
            super(name); 
        } 
        public void run(){ 
            // 獲取obj物件的同步鎖
            synchronized (obj) {
                try {
                    for(int i=0; i <10; i++){ 
                        System.out.printf("%s: %d\n", this.getName(), i); 
                        // i能被4整除時,休眠100毫秒
                        if (i%4 == 0)
                            Thread.sleep(100);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } 
    } 
}

複製程式碼

主執行緒main中啟動了兩個執行緒t1和t2。t1和t2在run()會引用同一個物件的同步鎖,即synchronized(obj)。在t1執行過程中,雖然它會呼叫Thread.sleep(100);但是,t2是不會獲取cpu執行權的。因為,t1並沒有釋放“obj所持有的同步鎖”! 注意,若我們註釋掉synchronized (obj)後再次執行該程式,t1和t2是可以相互切換的。