執行緒篇2:[- sleep、wait、notify、join、yield -]

張風捷特烈發表於2019-05-06
前言:執行緒的五種狀態

本文是執行緒篇的一個分支,主要結合我的理解,看一下sleep和wait以及執行緒的一些狀態
網上的圖看起來都有點醜,我自己畫了一幅:

執行緒狀態

1.New:       新建態:  new Thread ~ thread.start期間
2.Runnable:  可執行態: 可被CPU排程執行期間。
3.Running     執行態: 執行緒獲取CPU許可權進行執行
4.Blocked     阻塞狀態: 阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。
    |---等待阻塞:通過呼叫執行緒的wait()方法,讓執行緒等待某工作的完成。
    |---同步阻塞:執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用)時
    |---其他阻塞:通過呼叫執行緒的sleep()或join()或發出了I/O請求時
5.Dead        死亡狀態: 執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。
複製程式碼

一、Thread.sleep簡述

暫停當前執行緒,進入Blocked狀態,把cpu片段讓出給其他執行緒。

1.測試程式碼:
public class Main0 {
    static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) {
        new Thread(new Car()).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Ambulance()).start();
    }

    private static class Car implements Runnable {
        @Override
        public void run() {
            System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
            System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
        }
    }

    private static class Ambulance implements Runnable {
        @Override
        public void run() {
            System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車開始啟動,在路上跑");
            System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車跑到終點");
        }
    }
}
複製程式碼

2. 結果分析:注02:29代表當前時刻的分秒,即2分29秒

執行緒篇2:[- sleep、wait、notify、join、yield -]

---->[執行結果]----------------------
02:29:小汽車開始啟動,在路上跑
02:29:小汽車跑到終點
02:31:救護車開始啟動,在路上跑
02:31:救護車跑到終點
複製程式碼

二、執行緒內sleep以及synchronized

1.子執行緒加入休眠
private static class Car implements Runnable {
    @Override
    public void run() {
        System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
    }
}
複製程式碼

2.結果分析

Car執行緒的任務是睡5s,可見主執行緒的休眠沒有影響到Car子執行緒的執行(休眠)

執行緒篇2:[- sleep、wait、notify、join、yield -]

18:48:小汽車開始啟動,在路上跑
18:50:救護車開始啟動,在路上跑
18:50:救護車跑到終點
18:53:小汽車跑到終點
複製程式碼

3.當加鎖睡眠時

線上程1中加synchronized(這裡鎖用sdf物件,你也可以任意)

public class Main2 {
    static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
    public static void main(String[] args) {
        new Thread(new Car()).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Ambulance()).start();
    }

    private static class Car implements Runnable {
        @Override
        public void run() {
            synchronized (sdf){
                System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
            }
        }
    }

    private static class Ambulance implements Runnable {
        @Override
        public void run() {
            synchronized (sdf){
                System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車開始啟動,在路上跑");
                System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車跑到終點");
            }

        }
    }
}
複製程式碼

2.結果分析

Car執行緒和Ambulance執行緒在執行時使用同一把鎖,執行緒在休眠時不會釋放鎖
所以Ambulance執行緒需要等待Car執行緒執行完成,才能進行執行

執行緒篇2:[- sleep、wait、notify、join、yield -]

23:46:小汽車開始啟動,在路上跑
23:51:小汽車跑到終點
23:51:救護車開始啟動,在路上跑
23:51:救護車跑到終點
複製程式碼

三、Object#wait()方法的作用

執行緒t1呼叫A物件wait()方法,會釋放t1持有的鎖,讓t1進入等待佇列(Blocked狀態)
直到其他執行緒呼叫A物件的notify()方法notifyAll()方法t1進入同步佇列(Blocked狀態)
當t1獲得鎖後會進入就緒狀態Runnable,獲取CPU的排程權後會繼續執行,再貼一遍這個圖:

執行緒狀態


1.wait方法的使用

既然是釋放當前執行緒的鎖,那麼不須有鎖才行,而且必須用該鎖的物件呼叫wait方法
比如上面是用sdf物件加鎖的,必須使用sdf.wait();,否則會丟擲InterruptedException

private static class Car implements Runnable {
    @Override
    public void run() {
        synchronized (sdf) {
            System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
            try {
                Thread.sleep(3000);//模擬執行3s的任務之後
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            try {
                System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車緊急剎車....");
                sdf.wait();
                System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
        }
    }
}
private static class Ambulance implements Runnable {
    @Override
    public void run() {
        synchronized (sdf) {
            System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車開始啟動,在路上跑");
            System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車跑到終點");
        }
    }
}
複製程式碼

2.結果分析

在Car執行緒呼叫sdf.wait();後,鎖將被釋放,然後Ambulance執行緒就可以持有鎖執行了
如果不喚醒執行緒,執行緒將一直阻塞,就是根本停不下來。打個比方就是sdf是司機
wait()之後就把車鑰匙扔了,然後熄火了。鑰匙拿不回來,車就跑不了。需要notify()獲取車鑰匙

執行緒篇2:[- sleep、wait、notify、join、yield -]

30:48:小汽車開始啟動,在路上跑
30:51:小汽車緊急剎車....
30:51:救護車開始啟動,在路上跑
30:51:救護車跑到終點
然後就阻塞在這裡停不下來了....
複製程式碼

3.喚醒等待執行緒

注意wait和notify需要使用同一個物件,否則然並卵
在Ambulance執行緒跑完後喚醒Car執行緒,然後Car獲取所後會進入就緒態

private static class Ambulance implements Runnable {
    @Override
    public void run() {
        synchronized (sdf) {
            System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車開始啟動,在路上跑");
            System.out.println(sdf.format(System.currentTimeMillis()) + ":救護車跑到終點");
            sdf.notify();
        }
    }
}
複製程式碼

4.結果分析

執行緒篇2:[- sleep、wait、notify、join、yield -]

40:23:小汽車開始啟動,在路上跑
40:26:小汽車緊急剎車....
40:26:救護車開始啟動,在路上跑
40:26:救護車跑到終點
40:26:救護車:喂,哥們,醒醒,你可以開了...
40:26:小汽車開始啟動....
40:31:小汽車跑到終點
複製程式碼

四、Thread#join()方法 和Thread#yield

1.普通程式碼
public class Main6 {
    static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) {
        Thread car = new Thread(new Car());
        car.start();
        System.out.println(sdf.format(System.currentTimeMillis()) + ":main執行緒結束");

    }

    private static class Car implements Runnable {
        @Override
        public void run() {
            System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
            try {
                Thread.sleep(3000);//模擬執行3s的任務之後
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
        }
    }
}
複製程式碼

2.結果分析

執行緒篇2:[- sleep、wait、notify、join、yield -]

26:28:小汽車開始啟動,在路上跑
26:28:main執行緒結束
26:31:小汽車跑到終點
複製程式碼

3.join方法的使用

阻塞當前執行緒,知道join的執行緒結束或超時

執行緒篇2:[- sleep、wait、notify、join、yield -]

public class Main6 {
    static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) {
        Thread car = new Thread(new Car());
        car.start();
        try {
            car.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sdf.format(System.currentTimeMillis()) + ":main執行緒結束");

    }

    private static class Car implements Runnable {
        @Override
        public void run() {
            System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車開始啟動,在路上跑");
            try {
                Thread.sleep(3000);//模擬執行3s的任務之後
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽車跑到終點");
        }
    }
}
---->[列印結果]-----------------------------
31:53:小汽車開始啟動,在路上跑
31:56:小汽車跑到終點
31:56:main執行緒結束
複製程式碼

4.yield方法測試

讓出執行緒的cpu排程權,之後再一起搶奪。執行狀態-->就緒狀態

public class Main7 {
    public static void main(String[] args) {
        Thread car = new Car("car");
        Thread ambulance = new Car("Ambulance");
        car.start();
        ambulance.start();
    }

    private static class Car extends Thread {
        public Car(String name) {
            super(name);
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName() + "----" + i);
                if (i == 5) {
                    yield();
                }
            }
        }
    }
}
複製程式碼

執行了幾組資料,大部分滿足讓出排程權後,另一個執行緒執行,也有少量情況不是。
所以yield說明我現在不急,可以劃劃水,執行權可以讓出去。不過接下來誰執行還是不定的。

執行緒篇2:[- sleep、wait、notify、join、yield -]


五、小結

1.需要補充的點:

1.關於synchronized鎖這裡不展開,不瞭解的可以見:執行緒篇3:[-synchronized-]
2.關於synchronized鎖物件需要一致,否則鎖不住,然並卵。常用class物件,如Car.class
3.可以設定超時喚醒xxx.wait(3000),即3s後喚醒執行緒。
4.可以設定join超時xxx.join(3000),即3s後執行緒進入就緒態。
5.notifyAll:來著黑暗寒冬的隨從們,僕人們,士兵們,聽從克爾蘇加德的召喚吧!


2.簡單比較
item 從屬 是否釋放鎖 狀態轉變為 異常
sleep Thread 靜態方法 NO 阻塞 InterruptedException
wait Object 公共方法 YES 等待佇列 IllegalMonitorStateException+InterruptedException
notify/notifyAll Object 公共方法 -- 同步佇列 IllegalMonitorStateException
join Thread 公共方法 NO 阻塞 InterruptedException
yield Thread 公共方法 NO 可執行態

相關文章