java線程等待/通知機制及中斷

一、等待/通知機制

 在線程交互中經常需要對其進行一些控制,希望人爲地能夠讓線程按理想路線發展,在滿足某條件時進行執行操作而發生變化時,停止等待。

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


若打開 // myThreads[3].interrupt(); 註釋,則會報java.lang.InterruptedException 然後先結束4、5 

再結束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


 




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章