引入
由于線程是搶占式執行的,因此線程之間的執行的先后順序難以預知
但是實際開發中我們希望合理協調多個線程之間執行的先后順序.
這里的干預線程先后順序,并不是影響系統的調度策略(內核里調度線程,仍然是無序調度).
就是相當于在應用程序代碼中,讓后執行的線程主動放棄被調度的機會.就可以讓執行線程,先把對應的代碼執行完了.
完成這個協調工作,主要涉及到三個方法
wait()/wait(long timeout):讓當前線程進入準備狀態.
notify()/notifyAll():喚醒在當前對象上等待的線程.
注意:wait,notify,notifyAll都是Object類的方法.
wait()方法
一個線程重復拿到鎖,別的線程無法拿到鎖,這個情況稱為"線程餓死/饑餓".屬于概率性事件.雖然不像死鎖那樣嚴重.這種情況確實是bug.沒那么嚴重,但也極大地影響了程序的運行.
處理:使該線程主動放棄對鎖的爭奪/放棄去cpu調度執行(進入阻塞,也就是wait).一直到這個條件具備,再解除阻塞,參與鎖競爭.
wait做的事情:
1.使當前執行代碼的線程進行等待.(把線程放到等待隊列中).
2.釋放當前的鎖.
3.滿足一定條件時被喚醒,重新嘗試獲取這個鎖.
其中,1,2條可以讓其他線程有機會拿到鎖了.第三條指當其它線程調用notify的時候,wait解除阻塞.
wait結束等待的條件:
1.其它線程調用該對象的notify方法.
2.wait等待時間超時(wait方法提供一個帶有timeout參數的版本,來指定等待時間)->這是為了防止死等,具有魯棒性
3.其它線程調度該等待線程的interrupted方法,導致wait拋出InterruptException異常.
觀察wait()方法的使用:
public static void main(String[] args) throws InterruptedException {Object locker = new Object();synchronized (object) {System.out.println("等待中");object.wait();System.out.println("等待結束");}
}
這樣執行到object.wait()之后就會一直等待下去,那么程序肯定不能這樣一直等待下去了.這個時候就需要使用到了另外一個方法以喚醒,也就是notify().
notify方法
notify方法是喚醒等待的線程.
?1.方法notify()也要在同步方法或同步塊中調用,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對其發出通知notify,并使它們重新獲取該對象的對象鎖.
2.如果有多個線程等待,則有線程調度器隨機挑選出一個呈wait狀態的線程.(前提是操作的是同一個鎖).并沒有"先來后到"
3.在notify()方法后,當前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程將程序執行完,也就是退出同步代碼塊之后才會釋放對象鎖.
代碼示例:
public class ThreadTest5 {public static Object locker = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (locker) {System.out.println("t1 wait 之前");try {//t1執行起來之后,執行到這,就會先立即釋放鎖,進入wait方法(釋放鎖+阻塞等待)locker.wait();System.out.println("t1 wait 之后");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() -> {try {//t2執行起來之后,先進行sleep(3000)(這個sleep操作就可以讓t1先拿到鎖)//如果先notify雖然不會有副作用(不會出現異常之類的),但是wait就無法被喚醒,邏輯上有問題Thread.sleep(3000);//t2sleep結束之后,由于t1是wait狀態,t2就能拿到鎖//接下來打印t2notify之前,執行notify操作,這個操作就能喚醒t1(此時t1就從WAITING狀態恢復過來了)synchronized (locker) {System.out.println("t2 notify 之前");locker.notify();//但是由于t2此時還沒有釋放鎖,WAITING恢復之后,嘗試獲取鎖,就可能出現一個小小的阻塞,這個阻塞是由鎖競爭引起的//t1目前處于BLOCKED狀態,但是時間比較短,肉眼看不見System.out.println("t2 notify 之后");}//t2釋放鎖之后,就可以繼續執行t1} catch (InterruptedException e) {e.printStackTrace();}});t1.start();t2.start();}
}
notifyAll()方法
notify方法只是喚醒某一個等待線程.使用notifyAll方法可以一次喚醒所有的等待線程.
但是注意,這些線程在wait返回時,要重新獲取鎖,就會因為鎖的競爭,使這些線程實際上是一個一個串行執行的(誰先誰后拿到鎖是不一定的).?
?理解notify和notifyAll
notify只喚醒等待隊列中的一個線程.其它線程還是乖乖等著
notifyAll一下全都喚醒,需要這些線程重新競爭鎖.
相比之下,還是更傾向于notify.因為notifyAll全部喚醒之后,不好控制
?wait和sleep的對比
其實理論上wait和sleep完全是沒有可比性的,因為一個是用于線程之間通信的,一個是讓線程阻塞一段時間,唯一的共同的就是都可以讓線程放棄執行一段時間.
1.wait可通過notify喚醒,sleep通過Interrupt喚醒
2.使用wait的最主要的目標,一是不知道奪少時間的前提下使用的.所謂的"超時間",就是"兜底"
使用sleep,一定是直到多少時間的前提下使用的,這個操作不因該作為正常業務邏輯(通過異常喚醒,說明程序應該是出現特殊情況了)
3.wait搭配synchronized使用,sleep不需要
4.wait是Object的方法,sleep是Thread的靜態方法