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