一、等待/通知機制
在線程交互中經常需要對其進行一些控制,希望人爲地能夠讓線程按理想路線發展,在滿足某條件時進行執行操作而發生變化時,停止等待。
1、 使用sleep
在 if ( ) { } else { } 中使用sleep 對線程進行停止等待一段時間。 弊端:正常情況下 無法客觀預知需要等待的時間,在刻意睡眠一段時間後 很可能發現 依舊不適合由此線程執行之後的操作,或者睡眠過久。
2、 使用 while + sleep 循環判斷條件 使其睡眠 弊端:雖然能加快判斷條件的變化,但依舊難以確保及時性,會造成無端浪費。
3、wait +notify :在某條件發生情況下,線程A調用對象O 的wait() 方法進入等待狀態,當線程B調用對象O的notify() 或者notifyAll()方法後,線程A會接受通知,從其wait方法返回,執行後續操作。
java.lang.Obejct :
notify() 通知一個在對象上等待的線程,使其從wait方法返回(前提是該線程獲取到對象的鎖)
notifyAll() 通知所有等待在該對象上的線程 (注意,notify等通知時 不會釋放當前對象鎖)
wait() 調用該方法的線程進入Waiting狀態,只有被中斷或者由其他線程通知喚醒才能繼續(wait會導致線程釋放對象鎖)
wait(long) 超時等待一段時間,等待xx毫秒,若沒有收到通知 則超時返回
wait(long,int)超時精確到納秒
例:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class WaitAndNotify {
static boolean flag=true;
static Object lock =new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitThread=new Thread(new Wait(),"waitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(2);
Thread notifyThread=new Thread(new Notify(),"notifyThread");
notifyThread.start();
}
static class Wait implements Runnable{
@Override
public void run() {
synchronized (lock) {
//同步代碼塊
while (flag) {
try {
System.out.println(Thread.currentThread().getName()+" flag=true. wait@"+
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();<span style="white-space:pre"> </span>System.out.println("啊啊?");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//跳出while時
System.out.println(Thread.currentThread().getName()+" flag=false. wait@"+
new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable{
@Override
public void run() {
synchronized (lock) {
//獲取lock對象鎖,然後通知喚醒
System.out.println(Thread.currentThread().getName()+" hold lock. notify@"+
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag=false;
try {
Thread.sleep(3000);//notify之後線程睡眠3秒,驗證Wait類不能馬上輸出“啊啊?”
} catch (InterruptedException e) {
e.printStackTrace();
}
} //同步代碼塊結束後 釋放鎖
synchronized (lock) {
//再次加鎖
System.out.println(Thread.currentThread().getName()+" hold lock again. @"+
new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
運行結果:(注:在notifyThread裏的兩個同步代碼塊之間 可能會發生lock被Wait獲取 而使輸出結果的第三、四行互換)
waitThread flag=true. wait@16:00:44
notifyThread hold lock. notify@16:00:46
notifyThread hold lock again. @16:00:49
啊啊?
waitThread flag=false. wait@16:00:51
waitThread 獲取到lock對象鎖,之後調用wait()方法進入等待隊列,而同時會釋放掉對象鎖,狀態爲Waiting。 notifyThread獲取lock對象鎖後調用notify()方法通知一個等待線程(本例中僅一個等待線程),將其移到同步隊列,然後繼續執行自己的代碼,當釋放掉lock對象鎖後,waitThread線程纔有可能重新獲取lock並執行先前未完成的代碼。 藉助《Java併發編程的藝術》中一圖:
注意:
1)wait() notify() notifyAll() 等使用時 需要先對調用的對象加鎖獲取。
2)wait()之後 線程由Running轉變爲Waiting 將會把當前線程防止到對象的等待隊列。
3)並不是一旦使用notify() notifyAll() 就能實現線程執行wait()方法之後的代碼段,需要等發出notify()的線程先釋放對象鎖然後 等待線程重新獲得lock鎖 纔可以執行原wait()之後的操作!4)notify()方法將等待隊列中的一個等待線程從其中移到同步隊列中,而notifyAll()方法則是喚醒等待隊列中的所有線程,全部移到同步隊列,將Waiting改爲Blocked
等待方:①.獲取對象鎖 ②.若條件不滿足則調用對象wait(),被通知後要重新判斷條件(可能會僞喚醒)③.滿足條件後執行後續操作
通知方:①.獲取對象鎖 ②.改變條件 ③.通知等待的線程
二、中斷及join()
interrupt()只是改變中斷狀態而已. interrupt()不會中斷一個正在運行的線程。這一方法實際上完成的是,給受阻塞的線程拋出一箇中斷信號,這樣受阻線程就得以退出阻塞的狀態。如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。
join() 含義:若線程A執行某thread線程的join(),那麼當前線程A等待thread線程終止之後才能從join()返回
例: 每個線程調用前一個線程的join() 按順序結束操作。
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Thread[] myThreads =new Thread[5];
Thread previous=Thread.currentThread();
for(int i=0;i<5;i++){
myThreads[i]=new Thread(new Runner(previous),i+1+" Thread");
myThreads[i].start();
previous=myThreads[i];
}
// TimeUnit.SECONDS.sleep(3);//main sleep 3s
// myThreads[3].interrupt();
TimeUnit.SECONDS.sleep(3);//main sleep 3s
System.out.println(Thread.currentThread().getName()+" terminate");
}
static class Runner implements Runnable{
private Thread aThread;
public Runner( Thread aThread) {
this.aThread=aThread;
}
@Override
public void run() {
try {
aThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" terminate");
}
}
}
運行結果:
main terminate
1 Thread terminate
2 Thread terminate
3 Thread terminate
4 Thread terminate
5 Thread terminate
再結束mian 1 2 3 注:由於線程一直運行,在報錯的同時,已經在輸出4的語句
java.lang.InterruptedException
4 Thread terminate
at java.lang.Object.wait(Native Method)
at java.lang.Thread.join(Thread.java:1249)
at java.lang.Thread.join(Thread.java:1323)
at Test2$Runner.run(Test2.java:25)
at java.lang.Thread.run(Thread.java:745)
5 Thread terminate
main terminate
1 Thread terminate
2 Thread terminate
3 Thread terminate