1. 程式人生 > >【Java_多線程並發編程】基礎篇——線程狀態扭轉函數

【Java_多線程並發編程】基礎篇——線程狀態扭轉函數

多線程並發 sleep int syn 運行 yield cnblogs wait getname

1. wait() sleep() yield() join()用法與區別

本文提到的當前線程是指:當前時刻,獲得CPU資源正在執行的線程。

1.1 wait()方法

wait()方法定義在Object類中,它的作用是讓當前線程由“運行狀態”進入到“等待(阻塞)狀態”,同時釋放它所持有的鎖。被wait()阻塞的線程可通過notify() 方法或 notifyAll() 方法喚醒,達到就緒態。

Object類中關於等待/喚醒的API詳細信息如下:

wait() -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。

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

wait()和notify()示例:

// WaitTest.java的源碼
class ThreadA extends Thread {
public ThreadA(String name) {
super(name);
}

public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " call notify()");
// 喚醒當前等待的線程
notify();
}
}
}

public class WaitTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized (t1) {
try {
System.out.println(Thread.currentThread().getName() + " start t1");
// 啟動“線程t1”,使其進入就緒狀態
t1.start();
//阻塞當前正在執行的線程,並釋放其上的同步鎖資源
t1.wait();
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

運行結果:

main start t1
t1 call notify()
main continue

註意:(1)notify(), wait()依賴於“同步鎖”,而“同步鎖”是對象鎖持有,並且每個對象有且僅有一個。這就是為什麽notify(), wait()等函數定義在Object類,而不是Thread類中的原因。

(2 )這兩個方法只能在synchronized同步代碼塊中調用。

1.2 sleep()方法

sleep() 是定義在Thread類中的靜態方法,它的作用是讓當前線程會由“運行狀態”進入到“休眠(阻塞)狀態”。它和wait()方法的區別:

  • 這兩個方法來自不同的類分別是Thread和Object
  • 最主要是sleep方法沒有釋放當前線程持有的鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法(鎖代碼塊和方法鎖)。
  • wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用(使用範圍)
  • sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常

sleep()的用法實例:

// SleepTest.java的源碼
class ThreadA extends Thread{
    public ThreadA(String name){ 
        super(name); 
    } 
    public synchronized void run() { 
        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();
        }
    } 
} 

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

運行結果

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

1.3 yield()方法

yield()是定義在Thread類中的靜態方法,作用是讓當前線程讓步。即讓當前線程由“運行狀態”進入到“就緒狀態”,從而讓同優先級或更高優先級的線程有執行機會。但是,並不能保證在當前線程調用yield()方法之後,具有相同優先級的其它線程就一定能獲得執行機會,也有可能是當前線程又進入到“運行狀態”繼續運行。

yield()用法實例:

// YieldTest.java的源碼
class ThreadA extends Thread{
    public ThreadA(String name){ 
        super(name); 
    } 
    public synchronized void run(){ 
        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();
        } 
    } 
} 

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

運行結果:

t1 [5]:0
t2 [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]: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

結果說明
“線程t1”在能被4整數的時候,並沒有切換到“線程t2”。這表明,yield()雖然可以讓線程由“運行狀態”進入到“就緒狀態”;但是,它不一定會讓其它線程獲取CPU執行權(即,其它線程進入到“運行狀態”),即使這個“其它線程”與當前調用yield()的線程具有相同的優先級。

1.4 join() 方法

join()是定義在Thread類中的實例方法,它的作用是等待調用該方法的線程執行完畢,其它線程才能獲得執行機會。

join()的用法實例:

// JoinTest.java的源碼
public class JoinTest{ 

    public static void main(String[] args){ 
        try {
            ThreadA t1 = new ThreadA("t1"); // 新建“線程t1”

            t1.start();                     // 啟動“線程t1”
            t1.join();                        // 將“線程t1”加入到“主線程main”中,並且“主線程main()會等待它的完成”
            System.out.printf("%s finish\n", Thread.currentThread().getName()); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } 

    static class ThreadA extends Thread{

        public ThreadA(String name){ 
            super(name); 
        } 
        public void run(){ 
            System.out.printf("%s start\n", this.getName()); 

            // 延時操作
            for(int i=0; i <1000000; i++)
               ;

            System.out.printf("%s finish\n", this.getName()); 
        } 
    } 
}

運行結果

t1 start
t1 finish
main finish

總結:這四個方法中前三個都是針對當前線程的操作,與調用它的線程無關,只有最後一個方法是讓調用該方法的線程的操作

【Java_多線程並發編程】基礎篇——線程狀態扭轉函數