二、多執行緒[wait()和notify()/notifyAll()]
一、 wait()和notify()/notifyAll()
鎖,這裡講的就是同步鎖,同步執行的鎖,釋放完之後“鎖”之後就喪失了執行能力了,直到notify通知到wait方法,
(notify並不是釋放鎖),只是通知wait可以去競爭鎖了,wait並不是立刻得到鎖,鎖在別人手裡,等待別人釋放。
-
為什麼要用這個 ?
執行緒本身是作業系統中獨立的個體,但是執行緒與執行緒之間不是獨立的個體,因為它們彼此之間要相互通訊和協作。
想像一個場景,A執行緒做int型變數i的累加操作,B執行緒等待i到了10000就打印出i,怎麼處理?一個辦法就是,B執行緒while(i == 10000),這樣兩個執行緒之間就有了通訊,B執行緒不斷通過輪訓來檢測i == 10000這個條件。
這樣可以實現我們的需求,但是也帶來了問題:CPU把資源浪費了B執行緒的輪詢操作上,因為while操作並不釋放CPU資源,導致了CPU會一直在這個執行緒中做判斷操作。如果可以把這些輪詢的時間釋放出來,給別的執行緒用,就好了。
結論:而wait是進入執行緒等待池中等待,讓出系統資源。 -
wait()方法可以使呼叫該執行緒的方法釋放共享資源的鎖,然後從執行狀態退出,進入等待佇列,直到再次被喚醒。
-
notify()方法可以隨機喚醒等待佇列中等待同一共享資源的一個執行緒,並使得該執行緒退出等待狀態,進入可執行狀態
-
notifyAll()方法可以使所有正在等待佇列中等待同一共享資源的全部執行緒從等待狀態退出,進入可執行狀態
1、基本的用法
簡單地理解就是
wait 就是等待喚醒程式
notify 是喚醒程式
程式碼示例:
public class JobRun_wait_1 extends Thread { private Object o; public JobRun_wait_1(Object o) { this.o=o; } @Override public void run() { JobRun_notify_Function jobRun_notify_function=new JobRun_notify_Function(); jobRun_notify_function.wait(o); } } public class JobRun_notify_2 extends Thread { private Object o; public JobRun_notify_2(Object o) { this.o=o; } @Override public void run() { JobRun_notify_Function jobRun_notify_function=new JobRun_notify_Function(); jobRun_notify_function.notify(o); } } */ public class JobRun_notify_Function { //等待喚醒 public void wait(Object o){ synchronized (o){ try { System.out.println("wait--"+Thread.currentThread().getName()+"--start" ); o.wait(); System.out.println("wait--"+Thread.currentThread().getName()+"--end" ); } catch (InterruptedException e) { e.printStackTrace(); } } } //喚醒 通知 public void notify(Object o){ try{ synchronized (o){ System.out.println("notify--"+Thread.currentThread().getName()+"--start" ); o.notify(); System.out.println("notify--"+Thread.currentThread().getName()+"--end" ); } }catch (Exception e){ e.printStackTrace(); } } //喚醒 通知 public void notifyAll(Object o){ try{ synchronized (o){ System.out.println("notifyAll--"+Thread.currentThread().getName()+"--start" ); o.notifyAll(); System.out.println("notifyAll--"+Thread.currentThread().getName()+"--end" ); } }catch (Exception e){ e.printStackTrace(); } } } public static void main(String[] args){ Object o=new Object(); JobRun_wait_1 jobRun_wait=new JobRun_wait_1(o); jobRun_wait.start(); JobRun_notify_2 jobRun_notify=new JobRun_notify_2(o); jobRun_notify.start(); }
結果:
wait–Thread-0–start
notify–Thread-1–start
notify–Thread-1–end
wait–Thread-0–end
總結:可以看到,知道notify喚醒後才去執行執行緒0的結束方法
詳細舉例可以參考下這個:
https://blog.csdn.net/azhegps/article/details/63031562
2、多個wait時notify會喚醒哪個呢?
程式碼示例:
public static void main(String[] args) { Object o = new Object(); JobRun_wait_1 jobRun_wait = new JobRun_wait_1(o); JobRun_wait_1 jobRun_wait1 = new JobRun_wait_1(o); JobRun_wait_1 jobRun_wait2 = new JobRun_wait_1(o); jobRun_wait.start(); jobRun_wait1.start(); jobRun_wait2.start(); JobRun_notify_2 jobRun_notify = new JobRun_notify_2(o); jobRun_notify.start(); }
結果:
wait–Thread-1–start
notify–Thread-3–start
notify–Thread-3–end
wait–Thread-1–end
wait–Thread-0–start
wait–Thread-2–start
總結:如果有多個wait,再使用notify喚醒,notify只會隨機喚醒一個從上圖可以得出實際上是第一個執行的會被喚醒
2、interrupt在wait的作用
程式碼示例:
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
JobRun_wait_1 jobRun_wait = new JobRun_wait_1(o);
jobRun_wait.start();
Thread.sleep(3000);
jobRun_wait.interrupt();
}
結果:
wait–Thread-0–start
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at dome_thread.wait_notify.JobRun_notify_Function.wait(JobRun_notify_Function.java:14)
at dome_thread.wait_notify.JobRun_notify_1.run(JobRun_notify_1.java:16)
總結:interrupt能中斷等待喚醒的執行緒,之前也有提到過interrunpt只是給執行緒做通知。
3、notifyAll
利用Object物件的notifyAll()方法可以喚醒處於同一監視器下的所有處於wait的執行緒,舉個例子證明一下:
至於喚醒的順序,就和執行緒啟動的順序一樣,是虛擬機器隨機的。
程式碼示例:
public class JobRun_notify_3 extends Thread {
private Object o;
public JobRun_notify_3(Object o) {
this.o = o;
}
@Override
public void run(){
JobRun_notify_Function jnf=new JobRun_notify_Function();
jnf.notifyAll(o);
}
}
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
JobRun_wait_1 jobRun_wait = new JobRun_wait_1(o);
JobRun_wait_1 jobRun_wait1 = new JobRun_wait_1(o);
jobRun_wait.start();
jobRun_wait1.start();
Thread.sleep(1000); //保證wait都執行完後才去呼叫notify。
JobRun_notify_3 jobRun_notifyAll = new JobRun_notify_3(o);
jobRun_notifyAll.start();
}
結果:
wait–Thread-0–start
wait–Thread-1–start
notifyAll–Thread-2–start
notifyAll–Thread-2–end
wait–Thread-1–end
wait–Thread-0–end
總結:notifyAll可以喚醒所有wait,但是必須都wait所有的執行緒都在執行狀態。
4、join
join()方法的作用是等待執行緒銷燬。join()方法反應的是一個很現實的問題,
比如main執行緒的執行時間是1s,子執行緒的執行時間是10s,但是主執行緒依賴子執行緒執行完的結果,這時怎麼辦?
可以像生產者/消費者模型一樣,搞一個緩衝區,子執行緒執行完把資料放在緩衝區中,通知main執行緒,main執行緒去拿,這樣就不會浪費main執行緒的時間了。另外一種方法,就是join()了。
程式碼示例:
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--start" );
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--end" );
}
public static void main(String[] args) throws InterruptedException {
JobRun jr=new JobRun();
jr.start();
jr.join();
jr.interrupted();
System.out.println("job已經執行完成了");
}
結果:
Thread-0–start
Thread-0–end
job已經執行完成了
總結:主執行緒等了4秒鐘才去執行,這是接收到了子執行緒的通知了;很像wait,都是需要別人通知,但是wait,需要notiry通知,join是執行緒子執行緒銷燬時獲得通知。
5、join的實現
join()方法的一個重點是要區分出和sleep()方法的區別。join(2000)也是可以的,表示呼叫join()方法所在的執行緒最多等待2000ms,兩者的區別在於:
sleep(2000)不釋放鎖,join(2000)釋放鎖,因為join()方法內部使用的是wait(),因此會釋放鎖。看一下join(2000)的原始碼就知道了,join()其實和join(2000)一樣,無非是join(0)而已:
程式碼示例:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
既然呼叫join是被wait了,至於怎麼被notify的自行找資料 此處待更新…。
- 擴充套件
來自某位大神:
1 、 如何在IO執行中中斷無法正常完成的執行緒,這個問題不好回答,我嘗試一下回答看:
(1)等待響應,如果指的是網路等待響應的話,必然用到Socket,java.net.Socket類提供了setSoTimeout方法設定Socket超時時間,超時會丟擲SocketTimeoutException
(2)HttpClient類是常用的用於Http傳送請求、接收響應的類,這個類也可以設定超時時間,如果連線時間過長會丟擲ConnectTimeoutException,如果遲遲讀不到資料就會丟擲SocketTimeoutException
(3)如果是檔案IO的話,就比較難辦。常用的字元流、位元組流都是阻塞IO,IO獨佔一條執行緒,即使資料讀不到也無法釋放執行緒,我一下子也沒想到很好的辦法,之前我們的專案就因為IO一直在讀圖書封面資料但是沒有讀到,導致頁面一直在轉,打堆疊看到執行緒處於Runnnable狀態。阻塞IO,JDK的API也沒有提供給我們方法設定讀取資料的超時時間,所以要麼就從側面去解決這個問題,檔案IO操作起執行緒池,放在非同步執行緒裡面做,這樣即使檔案IO的操作阻塞了,也不會影響主業務流程
(4)除了阻塞IO還有非阻塞IO,也就是NIO,目前我還沒有系統地學習完NIO,所以不知道能不能解決這個問題,只是提出了另外一種IO的思路