看完了,發(fā)現(xiàn)對你有用的話點個贊吧! 持續(xù)努力更新學(xué)習(xí)中A├摹霜旧!多線程其他的部分點擊我的頭像查看更多哦儡率!
知識點
標(biāo)注:在學(xué)習(xí)中需要修改的內(nèi)容以及筆記全在這里 www.javanode.cn儿普,謝謝眉孩!有任何不妥的地方望糾正
線程創(chuàng)建
1. 創(chuàng)建方式
- 繼續(xù)Thread類
- 實現(xiàn)Runable接口
- 實現(xiàn)Callable接口勺像,并與Future吟宦、線程池結(jié)合使用殃姓,
1. 繼承Thread
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("this is new thread");
}
};
thread.start();
2. 實現(xiàn)runable接口
Thread thread1 = new Thread(new Runnable() {
public void run() {
System.out.println("impl runnable thread");
}
});
thread1.start();
3. 實現(xiàn)Callable接口
/**
* 3.實現(xiàn)callable接口蜗侈,提交給ExecutorService返回的是異步執(zhí)行的結(jié)果
*/
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> submit = executorService.submit(new Callable<String>() {
public String call() throws Exception {
return "three new callable thread";
}
});
String returnString = submit.get();
System.out.println(returnString);
2. 總結(jié)
- 實現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:
1):適合多個相同的程序代碼的線程去處理同一個資源
2):可以避免java中的單繼承的限制
3):增加程序的健壯性踏幻,代碼可以被多個線程共享该面,代碼和數(shù)據(jù)獨立
4):線程池只能放入實現(xiàn)Runable或callable類線程,不能直接放入繼承Thread的類
線程狀態(tài)切換
新建狀態(tài)(New):新創(chuàng)建了一個線程對象。
就緒狀態(tài)(Runnable):線程對象創(chuàng)建后界赔,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中咐低,變得可運行渊鞋,等待獲取CPU的使用權(quán)。
運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU特恬,執(zhí)行程序代碼徐钠。
阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán)尝丐,暫時停止運行爹袁。直到線程進(jìn)入就緒狀態(tài)失息,才有機會轉(zhuǎn)到運行狀態(tài)。阻塞的情況分三種:
等待阻塞:運行的線程執(zhí)行wait()方法邻梆,JVM會把該線程放入等待池中浦妄。(wait會釋放持有的鎖)
同步阻塞:運行的線程在獲取對象的同步鎖時校辩,若該同步鎖被別的線程占用宜咒,則JVM會把該線程放入鎖池中故黑。
其他阻塞:運行的線程執(zhí)行sleep()或join()方法场晶,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)钳宪。當(dāng)sleep()狀態(tài)超時吏颖、join()等待線程終止或者超時半醉、或者I/O處理完畢時劝术,線程重新轉(zhuǎn)入就緒狀態(tài)养晋。(注意,sleep是不會釋放持有的鎖)
- 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法绳泉,該線程結(jié)束生命周期圈纺。
線程調(diào)度
Java線程的實現(xiàn):Java線程模型是基于操作系統(tǒng)原生線程模型來實現(xiàn)的;
線程模型只對線程的并發(fā)規(guī)模和操作成本產(chǎn)生影響蛾娶,對Java程序的編寫和運行過程來說灯谣,并沒有什么不同。
1. 線程優(yōu)先級
時分形式是現(xiàn)代操作系統(tǒng)采用的基本線程調(diào)度形式
蛔琅,操作系統(tǒng)將CPU資源分為一個個的時間片胎许,并分配給線程,線程使用獲取的時間片執(zhí)行任務(wù)罗售,時間片使用完之后,操作系統(tǒng)進(jìn)行線程調(diào)度寨躁,其他獲得時間片的線程開始執(zhí)行穆碎;那么,一個線程能夠分配得到的時間片的多少決定了線程使用多少的處理器資源职恳,線程優(yōu)先級則是決定線程可以獲得多或少的處理器資源的線程屬性
所禀;
可以通過設(shè)置線程的優(yōu)先級方面,使得線程獲得處理器執(zhí)行時間的長短有所不同,但采用這種方式來實現(xiàn)線程獲取處理器執(zhí)行時間的長短并不可靠(因為系統(tǒng)的優(yōu)先級和Java中的優(yōu)先級不是一一對應(yīng)的色徘,有可能Java中多個線程優(yōu)先級對應(yīng)于系統(tǒng)中同一個優(yōu)先級)恭金;Java中有10個線程優(yōu)先級,從1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY)褂策,默認(rèn)優(yōu)先級為5横腿;因此,程序的正確性不能夠依賴線程優(yōu)先級的高低來判斷斤寂;
2. 線程調(diào)度分類
線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過程耿焊;主要調(diào)度方式有:搶占式線程調(diào)度、協(xié)同式線程調(diào)度
;
2.1 搶占式線程調(diào)度
每個線程由系統(tǒng)來分配執(zhí)行時間,線程的切換不由線程本身決定提澎;Java默認(rèn)使用的線程調(diào)度方式是搶占式線程調(diào)度
镰踏;我們可以通過Thread.yield()使當(dāng)前正在執(zhí)行的線程讓出執(zhí)行時間,但是蒂培,卻沒有辦法使線程去獲取執(zhí)行時間再愈;
2.2 協(xié)同式線程調(diào)度
每個線程的執(zhí)行時間由線程本身來控制,線程執(zhí)行完任務(wù)后主動通知系統(tǒng)护戳,切換到另一個線程上翎冲;
2.3 兩種線程調(diào)度方式的優(yōu)缺點
協(xié)同式的優(yōu)點:實現(xiàn)簡單,可以通過對線程的切換控制避免線程安全問題媳荒;
協(xié)同式的缺點:一旦當(dāng)前線程出現(xiàn)問題抗悍,將有可能影響到其他線程的執(zhí)行,最終可能導(dǎo)致系統(tǒng)崩潰钳枕;
搶占式的優(yōu)點:一個線程出現(xiàn)問題不會影響到其他線程的執(zhí)行(線程的執(zhí)行時間是由系統(tǒng)分配的缴渊,因此,系統(tǒng)可以將處理器執(zhí)行時間分配給其他線程從而避免一個線程出現(xiàn)故障導(dǎo)致整個系統(tǒng)崩潰的現(xiàn)象發(fā)生)
2.4 結(jié)論
Java中鱼炒,線程的調(diào)度策略主要是搶占式調(diào)度策略衔沼,正是因為搶占式調(diào)度策略,導(dǎo)致多線程程序執(zhí)行過程中昔瞧,實際的運行過程與我們邏輯上理解的順序存在較大的區(qū)別指蚁,也就是多線程程序的執(zhí)行具有不確定性,從而會導(dǎo)致一些線程安全性問題的發(fā)生自晰;
3. 調(diào)度方式
3.1 調(diào)度的方式
- 線程睡眠:Thread.sleep(long millis)方法凝化,使
線程轉(zhuǎn)到阻塞狀態(tài)
。millis參數(shù)設(shè)定睡眠的時間酬荞,以毫秒為單位搓劫。當(dāng)睡眠結(jié)束后劣光,就轉(zhuǎn)為就緒(Runnable)狀態(tài)。sleep()平臺移植性好糟把。 - 線程等待:Object類中的wait()方法绢涡,導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 喚醒方法遣疯。這個兩個喚醒方法也是Object類中的方法雄可,行為等價于調(diào)用 wait(0) 一樣。
- 線程讓步:Thread.yield() 方法缠犀,暫停當(dāng)前正在執(zhí)行的線程對象数苫,把執(zhí)行機會讓給相同或者更高優(yōu)先級的線程。
- 線程加入:join()方法辨液,等待其他線程終止虐急。在當(dāng)前線程中調(diào)用另一個線程的join()方法,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài)滔迈,直到另一個進(jìn)程運行結(jié)束止吁,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。
- 線程喚醒:Object類中的notify()方法燎悍,喚醒在此對象監(jiān)視器上等待的單個線程敬惦。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程谈山。選擇是任意性的俄删,并在對實現(xiàn)做出決定時發(fā)生。線程通過調(diào)用其中一個 wait 方法奏路,在對象的監(jiān)視器上等待畴椰。
3.2 深入理解(重要)
sleep()
sleep(long millis): 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)
join()
join():指等待t線程終止。
join是Thread類的一個方法鸽粉,啟動線程后直接調(diào)用斜脂,即join()的作用是:等待該線程終止,也就是在子線程調(diào)用了join()方法后面的代碼潜叛,只有等到子線程結(jié)束了才能執(zhí)行秽褒。
案例:
在很多情況下,主線程生成并啟動了子線程威兜,如果子線程里要進(jìn)行大量的耗時的運算销斟,主線程往往將于子線程之前結(jié)束,但是如果主線程處理完其他的事務(wù)后椒舵,需要用到子線程的處理結(jié)果蚂踊,也就是主線程需要等待子線程執(zhí)行完成之后再結(jié)束,這個時候就要用到j(luò)oin()方法了笔宿。
代碼:
package cn.javanode.thread.joinUse;
/**
* @author xgt(小光頭)
* @version 1.0
* @date 2021-1-10 9:52
*/
public class JoinUseRunnableThread {
static class joinThrad implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +" 線程運行開始!");
for (int i = 0; i < 5; i++) {
System.out.println("子線程"+Thread.currentThread().getName() +"運行 : "+i);
try {
Thread.sleep((int)Math.random()*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
System.out.println("main方法的線程開啟");
Thread joinThread = new Thread(new joinThrad());
joinThread.setName("JoinThread");
joinThread.start();
//添加join 子線程調(diào)用了join()方法后面的代碼犁钟,只有等到子線程結(jié)束了才能執(zhí)行棱诱。
try {
joinThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main方法的線程結(jié)束");
}
}
yield()
yield():暫停當(dāng)前正在執(zhí)行的線程對象,并執(zhí)行其他線程
yield()做的是讓當(dāng)前運行線程回到可運行狀態(tài)涝动,以允許具有相同優(yōu)先級的其他線程獲得運行機會迈勋。因此,使用yield()的目的是讓相同優(yōu)先級的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行醋粟。但是靡菇,實際中無法保證yield()達(dá)到讓步目的,因為讓步的線程還有可能被線程調(diào)度程序再次選中米愿。
結(jié)論:yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)厦凤。在大多數(shù)情況下,yield()將導(dǎo)致線程從運行狀態(tài)轉(zhuǎn)到可運行狀態(tài)育苟,但有可能沒有效果较鼓。
package cn.javanode.thread.yieldUse;
/**
* @author xgt(小光頭)
* @version 1.0
* @date 2021-1-10 10:57
*/
public class ThreadYieldDemo {
static class yieldThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"runing time="+i);
if(i==30){
Thread.yield();
}
}
}
}
public static void main(String[] args) {
//yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)。在大多數(shù)情況下违柏,yield()將導(dǎo)致線程從運行狀態(tài)轉(zhuǎn)到可運行狀態(tài)博烂,但有可能沒有效果。
Thread yt1 = new Thread(new yieldThread());
yt1.setName("ytthread1");
Thread yt2 = new Thread(new yieldThread());
yt2.setName("ytthread2");
yt1.start();
yt2.start();
}
}
3.3 補充
sleep()和yield()的區(qū)別
- sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài)勇垛,所以執(zhí)行sleep()的線程在指定的時間內(nèi)肯定不會被執(zhí)行脖母;
- yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài)士鸥,所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行闲孤。
補充:
sleep 方法使當(dāng)前運行中的線程睡眠一段時間,進(jìn)入不可運行狀態(tài)烤礁,這段時間的長短是由程序設(shè)定的讼积,yield 方法使當(dāng)前線程讓出 CPU 占有權(quán),但讓出的時間是不可設(shè)定的脚仔。實際上勤众,yield()方法對應(yīng)了如下操作:先檢測當(dāng)前是否有相同優(yōu)先級的線程處于同可運行狀態(tài),如有鲤脏,則把 CPU 的占有權(quán)交給此線程们颜,否則,繼續(xù)運行原來的線程猎醇。所以yield()方法稱為“退讓”窥突,它把運行機會讓給了同等優(yōu)先級的其他線程
- sleep 方法允許較低優(yōu)先級的線程獲得運行機會,
- yield() 方法執(zhí)行時硫嘶,當(dāng)前線程仍處在可運行狀態(tài)阻问,所以,不可能讓出較低優(yōu)先級的線程時獲得 CPU 占有權(quán)沦疾。
補充:
在一個運行系統(tǒng)中称近,如果較高優(yōu)先級的線程沒有調(diào)用 sleep 方法第队,又沒有受到 I\O 阻塞,那么刨秆,較低優(yōu)先級線程只能等待所有較高優(yōu)先級的線程運行結(jié)束凳谦,才有機會運行。
wait和sleep區(qū)別
共同點:
多線程的環(huán)境下衡未,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù)晾蜘,并返回。
wait()和sleep()都可以通過interrupt()方法 打斷線程的暫停狀態(tài) 眠屎,從而使線程立刻拋出InterruptedException剔交。
如果線程A希望立即結(jié)束線程B,則可以對線程B對應(yīng)的Thread實例調(diào)用interrupt方法改衩。如果此刻線程B正在wait/sleep /join岖常,則線程B會立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結(jié)束線程葫督。
需要注意的是竭鞍,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的橄镜。對某一線程調(diào)用 interrupt()時偎快,如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會拋出InterruptedException洽胶。但是晒夹,一旦該線程進(jìn)入到 wait()/sleep()/join()后,就會立刻拋出InterruptedException 姊氓。
不同點:
-
所屬對象不同
:Thread類的方法:sleep(),yield()等 丐怯。Object對象的方法:wait()和notify()等 -
是否釋放鎖
:每個對象都有一個鎖來控制同步訪問。Synchronized關(guān)鍵字可以和對象的鎖交互翔横,來實現(xiàn)線程的同步读跷。sleep方法沒有釋放鎖,而wait方法釋放了鎖禾唁,使得其他線程可以使用同步控制塊或者方法效览。sleep()睡眠時,保持對象鎖荡短,仍然占有該鎖丐枉;wait()睡眠時,釋放對象鎖肢预。但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態(tài)矛洞,從而使線程立刻拋出InterruptedException(但不建議使用該方法)。 -
使用的位置不同
wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用沼本,而sleep可以在任何地方使用 -
異常捕獲
:sleep必須捕獲異常噩峦,而wait,notify和notifyAll不需要捕獲異常
補充:
sleep()方法
sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài)(阻塞當(dāng)前線程)抽兆,讓出CUP的使用识补、目的是不讓當(dāng)前線程獨自霸占該進(jìn)程所獲的CPU資源,以留一定時間給其他線程執(zhí)行的機會;
sleep()是Thread類的Static(靜態(tài))的方法辫红;因此他不能改變對象的機鎖凭涂,所以當(dāng)在一個Synchronized塊中調(diào)用Sleep()方法時,線程雖然休眠了贴妻,但是對象的機鎖并木有被釋放切油,其他線程無法訪問這個對象(即使睡著也持有對象鎖)。在sleep()休眠時間期滿后名惩,該線程不一定會立即執(zhí)行澎胡,這是因為其它線程可能正在運行而且沒有被調(diào)度為放棄執(zhí)行,除非此線程具有更高的優(yōu)先級娩鹉。
wait())方法
wait()方法是Object類里的方法攻谁;當(dāng)一個線程執(zhí)行到wait()方法時,它就進(jìn)入到一個和該對象相關(guān)的等待池中弯予,同時失去(釋放)了對象的機鎖(暫時失去機鎖戚宦,wait(long timeout)超時時間到后還需要返還對象鎖);其他線程可以訪問锈嫩;wait()使用notify或者notifyAlll或者指定睡眠時間來喚醒當(dāng)前等待池中的線程受楼。
wiat()必須放在synchronized block中,否則會在program runtime時扔出”java.lang.IllegalMonitorStateException“異常祠挫。
wait()和notify()那槽、notifyAll()
這三個方法用于協(xié)調(diào)多個線程對共享數(shù)據(jù)的存取
,所以必須在synchronized語句塊內(nèi)使用
等舔。synchronized關(guān)鍵字用于保護(hù)共享數(shù)據(jù),阻止其他線程對共享數(shù)據(jù)的存取糟趾,但是這樣程序的流程就很不靈活了慌植,如何才能在當(dāng)前線程還沒退出synchronized數(shù)據(jù)塊時讓其他線程也有機會訪問共享數(shù)據(jù)呢?此時就用這三個方法來靈活控制义郑。wait() 方法使當(dāng)前線程暫停執(zhí)行并釋放對象鎖標(biāo)示蝶柿,讓其他線程可以進(jìn)入synchronized數(shù)據(jù)塊,當(dāng)前線程被放入對象等待池中非驮。當(dāng)調(diào)用notify()方法后交汤,將從對象的等待池中移走一個任意的線程并放到鎖標(biāo)志等待池中,只有鎖標(biāo)志等待池中線程能夠獲取鎖標(biāo)志;如果鎖標(biāo)志等待池中沒有線程芙扎,則notify()不起作用星岗。notifyAll() 從對象等待池中移走所有等待那個對象的線程并放到鎖標(biāo)志等待池中。(下面的線程間通信部分會細(xì)說
)
wait戒洼,notify 和notifyAll 這些方法為什么不在 thread類里面
Java提供的鎖是對象級的而不是線程級的俏橘,每個對象都有鎖,通過線程來獲得 由于 wait notify和notifyAll 都是鎖級別的的操作圈浇,所以把他們定義在Object類中因為鎖屬于對象寥掐。
線程間通信(重要)
如果你的多線程程序僅僅是每個線程獨立完成各自的任務(wù),相互之間并沒有交互和協(xié)作磷蜀,那么召耘,你的程序是無法發(fā)揮出多線程的優(yōu)勢的,只有有交互的多線程程序才是有意義的程序褐隆,否則怎茫,還不如使用單線程執(zhí)行多個方法實現(xiàn)程序來的簡單、易懂妓灌、有效轨蛤!
1. java等待通知機制
場景:線程A修改了對象O的值,線程B感知到對象O的變化虫埂,執(zhí)行相應(yīng)的操作祥山,這樣就是一個線程間交互的場景;可以看出掉伏,這種方式缝呕,相當(dāng)于線程A是發(fā)送了消息,線程B接收到消息斧散,進(jìn)行后續(xù)操作供常,是不是很像生產(chǎn)者與消費者的關(guān)系?我們都知道鸡捐,生產(chǎn)者與消費者模式可以實現(xiàn)解耦栈暇,使得程序結(jié)構(gòu)上具備伸縮性;
- 一種簡單的方式是箍镜,線程B每隔一段時間就輪詢對象O是否發(fā)生變化源祈,如果發(fā)生變化,就結(jié)束輪詢色迂,執(zhí)行后續(xù)操作香缺;
缺點: 這種方式不能保證對象O的變更及時被線程B感知,同時歇僧,不斷地輪詢也會造成較大的開銷图张;分析這些問題的癥結(jié)在哪?其實,可以發(fā)現(xiàn)狀態(tài)的感知是拉取的祸轮,而不是推送的兽埃,因此才會導(dǎo)致這樣的問題產(chǎn)生
- Java內(nèi)置的經(jīng)典的等待/通知機制
那就是wait()/notify()/notifyAll(),重要 便于理解例子
/**
如果在調(diào)用了此方法之后倔撞,其他線程調(diào)用notify()或者notifyAll()方法之前讲仰,線程被中斷,則會清除中斷標(biāo)志并拋出異常
* 當(dāng)前線程必須擁有對象O的監(jiān)視器痪蝇,調(diào)用了對象O的此方法會導(dǎo)致當(dāng)前線程釋放已占有的監(jiān)視器鄙陡,并且等待
* 其它線程對象O的notify()或者notifyAll()方法,當(dāng)其它線程執(zhí)行了這兩個方法中的一個之后躏啰,并且
* 當(dāng)前線程獲取到處理器執(zhí)行權(quán)趁矾,就可以嘗試獲取監(jiān)視器,進(jìn)而繼續(xù)后續(xù)操作的執(zhí)行
*/
public final void wait() throws InterruptedException {
wait(0);
}
/**
喚醒等待在對象O的監(jiān)視器上的一個線程给僵,如果多個線程等待在對象O的監(jiān)視器上毫捣,那么將會選擇其中的一個進(jìn)行喚醒
* 被喚醒的線程只有在當(dāng)前線程釋放鎖之后才能夠繼續(xù)執(zhí)行.
* 被喚醒的線程將會與其他線程一同競爭對象O的監(jiān)視器鎖
* 這個方法必須在擁有對象O的監(jiān)視器的線程中進(jìn)行調(diào)用
* 同一個時刻,只能有一個線程擁有該對象的監(jiān)視器
*/
public final native void notify();
/**
*喚醒等待在對象O的監(jiān)視器上的所有線程
* 被喚醒的線程只有在當(dāng)前線程釋放鎖之后才能夠繼續(xù)執(zhí)行.
* 被喚醒的線程將會與其他線程一同競爭對象O的監(jiān)視器鎖
* 這個方法必須在擁有對象O的監(jiān)視器的線程中進(jìn)行調(diào)用
* 同一個時刻帝际,只能有一個線程擁有該對象的監(jiān)視器
*/
public final native void notifyAll();
2. 經(jīng)典的等待/通知機制代碼
package cn.javanode.thread.JavaWaitAndConsumer;
public class WaitAndNotify {
//輪詢標(biāo)志位
private static boolean stop = false;
//監(jiān)視器對應(yīng)的對象
private static Object monitor = new Object();
//等待線程
static class WaitThread implements Runnable{
@Override
public void run() {
synchronized(monitor){
//循環(huán)檢測標(biāo)志位是否變更
while(!stop){
try {
//標(biāo)志位未變更蔓同,進(jìn)行等待 鎖釋放,整個線程等待
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被喚醒后獲取到對象的監(jiān)視器之后執(zhí)行的代碼
System.out.println("1Thread "+Thread.currentThread().getName()+" is awakened at first time");
stop = false;
}
//休眠1秒之后蹲诀,線程角色轉(zhuǎn)換為喚醒線程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//與上述代碼相反的邏輯
synchronized(monitor){
while(stop){
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
monitor.notify();
stop = true;
System.out.println("2Thread "+ Thread.currentThread().getName()+" notifies the waitted thread at first time");
}
}
}
//通知線程
static class NotifyThread implements Runnable{
@Override
public void run() {
synchronized (monitor){
while(stop){
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
stop = true;
monitor.notify();
System.out.println("3Thread "+ Thread.currentThread().getName()+" notifies the waitted thread at first time");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (monitor){
while(!stop){
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4Thread "+Thread.currentThread().getName()+" is awakened at first time");
}
}
}
public static void main(String[] args){
Thread waitThread = new Thread(new WaitThread());
waitThread.setName("waitThread");
Thread notifyThread = new Thread(new NotifyThread());
notifyThread.setName("notifyThread");
waitThread.start();
notifyThread.start();
}
}
通過上述代碼斑粱,可以提煉出等待通知機制的經(jīng)典模式:
等待方實現(xiàn)步驟:
- 加鎖同步
- 條件不滿足,進(jìn)入等待脯爪,被喚醒之后则北,繼續(xù)檢查條件是否滿足(循環(huán)檢測)
- 條件滿足,退出循環(huán)痕慢,繼續(xù)執(zhí)行后續(xù)代碼
synchronized(obj){
while(condition不滿足){
obj.wait();
}
//后續(xù)操作
}
通知方實現(xiàn)步驟:
- 加鎖同步
- 條件不滿足尚揣,跳過循環(huán)檢測
- 設(shè)置條件并喚醒線程
synchronized(obj){
while(condition不滿足){
obj.wait();
}
更新condition
obj.notify();
//后續(xù)操作
}
3. 生產(chǎn)者消費者代碼
package cn.javanode.thread.JavaWaitAndConsumer;
public class ProducerAndConsumer {
//商品庫存
private static int storeMount = 0;
//監(jiān)視器對應(yīng)的對象
private static Object monitor = new Object();
//生產(chǎn)者線程
static class ProducerThread implements Runnable{
@Override
public void run() {
try {
produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void produce() throws InterruptedException {
while(true){
synchronized(monitor){
//循環(huán)檢測庫存是否大于0,大于0表示還有商品可以消費掖举,線程等待消費者消費商品
while(storeMount > 0){
monitor.wait();
}
//被喚醒后獲取到對象的監(jiān)視器之后執(zhí)行的代碼
System.out.println("Thread "+Thread.currentThread().getName()+" begin produce goods");
//生產(chǎn)商品
storeMount = 1;
//喚醒消費者
monitor.notify();
Thread.sleep(1000);
}
}
}
}
//消費者線程
static class ConsumerThread implements Runnable{
@Override
public void run() {
try {
consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void consume() throws InterruptedException {
while(true){
synchronized (monitor){
//檢測庫存是否不為0快骗,如果不為0,那么有商品可供消費拇泛,否則等待生產(chǎn)者生產(chǎn)商品
while(storeMount == 0){
monitor.wait();
}
//消費商品
storeMount = 0;
//喚醒生產(chǎn)者線程
monitor.notify();
System.out.println("Thread "+Thread.currentThread().getName()+" begin consume goods");
Thread.sleep(1000);
}
}
}
}
public static void main(String[] args){
Thread producerThread = new Thread(new ProducerThread());
producerThread.setName("producerThread");
Thread consumerThread = new Thread(new ConsumerThread());
consumerThread.setName("consumerThread");
producerThread.start();
consumerThread.start();
}
}
上述代碼示例演示了一個生產(chǎn)者生產(chǎn)商品和一個消費者消費商品的場景滨巴,對于一個生產(chǎn)者多個消費者、多個生產(chǎn)者一個消費者俺叭、多個生產(chǎn)者多個消費者等場景,只需要將喚醒的方法換為notifyAll()即可泰偿,否則熄守,會出現(xiàn)饑餓現(xiàn)象!
4. 總結(jié)
以上就是本文敘述的所有內(nèi)容,本文首先對于給出Java中線程調(diào)度形式裕照,引出多線程編程中需要解決的線程安全問題攒发,并分析線程安全問題,給出解決線程安全問題的常用手段(加鎖同步)晋南,最后惠猿,結(jié)合Java內(nèi)置的等待通知機制,進(jìn)行了樣例代碼的展示以及分析负间,給出了經(jīng)典的等待通知機制的編程范式偶妖,最后,基于等待通知機制給A出了生產(chǎn)者消費者模式的實現(xiàn)樣例政溃,希望本文能給想要學(xué)習(xí)多線程編程的朋友一點幫助趾访,如有不正確的地方,還望指出董虱,十分感謝扼鞋!
注意細(xì)節(jié)(了解)
- 線程分類
- 用戶線程:大多數(shù)線程都是用戶線程,用于完成業(yè)務(wù)功能
- 守護(hù)線程:支持型線程愤诱,主要用于后臺調(diào)度以及支持性工作云头,比如GC線程,當(dāng)JVM中不存在非守護(hù)線程時淫半,JVM將會退出
- Thread.setDaemon(true)來設(shè)置線程屬性為守護(hù)線程溃槐,該操作必須在線程調(diào)用start()方法之前執(zhí)行
- 守護(hù)線程中的finally代碼塊不一定會執(zhí)行,因此不要寄托于守護(hù)線程中的finally代碼塊來完成資源的釋放
- 線程交互的方式
- join
- sleep/interrupt
- wait/notify
- 啟動線程的方式
- 只能通過線程對象調(diào)用start()方法來啟動線程
- start()方法的含義是撮慨,當(dāng)前線程(父線程)同步告知虛擬機竿痰,只要線程規(guī)劃期空閑,就應(yīng)該立即啟動調(diào)用了start()方法的線程
- 線程啟動前砌溺,應(yīng)該設(shè)置線程名影涉,以便使用Jstack分析程序中線程運行狀況時,起到提示性作用
- 終止線程的方式
- 中斷檢測機制
- 線程通過調(diào)用目標(biāo)線程的interrupt()方法對目標(biāo)線程進(jìn)行中斷標(biāo)志规伐,目標(biāo)線程通過檢測自身的中斷標(biāo)志位(interrupted()或isInterrupted())來響應(yīng)中斷蟹倾,進(jìn)行資源的釋放以及最后的終止線程操作;
- 拋出InterruptedException異常的方法在拋出異常之前猖闪,都會將該線程的中斷標(biāo)志位清除鲜棠,然后拋出異常
- suspend()/resume()(棄用)
- 調(diào)用后,線程不會釋放已經(jīng)占有的資源培慌,容易引發(fā)死鎖問題
- stop()(棄用)
- 調(diào)用之后不一定保證線程資源的釋放
- 中斷檢測機制
- 鎖釋放的情況:
- 同步方法或同步代碼塊的執(zhí)行結(jié)束(正常豁陆、異常結(jié)束)
- 同步方法或同步代碼塊鎖對象調(diào)用wait方法
- 鎖不會釋放的情況:
- 調(diào)用Thead類的靜態(tài)方法yield()以及sleep()
- 調(diào)用線程對象的suspend()