【Java_多線程並發編程】基礎篇——線程狀態扭轉函數
1. wait() sleep() yield() join()用法與區別
本文提到的當前線程是指:當前時刻,獲得CPU資源正在執行的線程。
1.1 wait()方法
wait()方法定義在Object類中,它的作用是讓當前線程由“運行狀態”進入到“等待(阻塞)狀態”,同時釋放它所持有的鎖。被wait()阻塞的線程可通過notify() 方法或 notifyAll() 方法喚醒,達到就緒態。
Object類中關於等待/喚醒的API詳細信息如下:
wait() -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout, int nanos) -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)。
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_多線程並發編程】基礎篇——線程狀態扭轉函數