






























































































首先,我們討論一下進(jìn)程,進(jìn)程是操作系統(tǒng)級(jí)別下,單獨(dú)執(zhí)行的一個(gè)任務(wù)。
Win32 Unix都是多任務(wù)操作系統(tǒng)。
多任務(wù),并發(fā)執(zhí)行是一個(gè)宏觀概念,實(shí)際微觀串行。
CPU同一時(shí)間刻只能執(zhí)行一個(gè)任務(wù)。
OS負(fù)責(zé)進(jìn)程調(diào)度,使用CPU,也就是獲得時(shí)間片。
在一個(gè)進(jìn)程中,再可以分為多個(gè)程序順序執(zhí)行流,每個(gè)執(zhí)行流就是一個(gè)線程。
分配CPU時(shí)間片的依然是CPU,多線程時(shí),程序會(huì)變慢,每個(gè)線程分配到的時(shí)間片少了。
進(jìn)程與線程的區(qū)別,進(jìn)程是數(shù)據(jù)獨(dú)占的(獨(dú)立數(shù)據(jù)空間),線程是數(shù)據(jù)共享的(這也是線程之間通訊容易的原因,不需要傳遞數(shù)據(jù))。
Java是語言級(jí)支持多線程的,體現(xiàn)在有現(xiàn)成的封裝類(java.lang.Thread)完成了必要的并發(fā)細(xì)節(jié)的工作(與操作系統(tǒng)打交道,分配PID等)。
兩種方式來得到一個(gè)線程對(duì)象。























一個(gè)線程對(duì)象--〉代表著一個(gè)線程--〉一個(gè)順序執(zhí)行流(run方法)
這個(gè)程序有兩個(gè)線程,一個(gè)是main主線程,它調(diào)用了t1.start(),這是t1線程只是就緒狀態(tài),還沒有真正啟動(dòng)線程,main主線程結(jié)束了!!t1運(yùn)行。兩個(gè)線程都退出了,進(jìn)程完結(jié)。
一個(gè)進(jìn)程退出,要等待進(jìn)程中所有線程都退出,再退出虛擬機(jī)。
方式2,實(shí)現(xiàn)java.lang.Runable接口,這是這個(gè)類的對(duì)象是一個(gè)目標(biāo)對(duì)象,而不能理解為是一個(gè)線程對(duì)象。







































不要調(diào)用run()方法,它只是執(zhí)行一下普通的方法,并不會(huì)啟動(dòng)單獨(dú)的線程。
上面只是線程狀態(tài)圖。
在某一個(gè)時(shí)間內(nèi),處于運(yùn)行狀態(tài)的線程,執(zhí)行代碼,注意可能多個(gè)線程多次執(zhí)行代碼。
CPU會(huì)不斷從可運(yùn)行狀態(tài)線程調(diào)入運(yùn)行,不會(huì)讓CPU空閑。
Thread.sleep(1000);當(dāng)前線程睡眠1秒鐘,休眠后->進(jìn)入阻塞->休眠結(jié)束->回到可運(yùn)行狀態(tài)。
在run(),有異常拋出,必須try{}catch(Exception e){},不能throws Exception,因?yàn)閞un()方法覆蓋不能拋例外。
能進(jìn)入運(yùn)行狀態(tài),只能由操作系統(tǒng)來調(diào)度。
一旦sleep-〉阻塞->交出程序執(zhí)行權(quán)。
等待用戶輸入,輸入輸出設(shè)備占用CPU,處于阻塞的線程沒有機(jī)會(huì)運(yùn)行,輸入完畢,重新進(jìn)入可運(yùn)行狀態(tài)。
第三種進(jìn)入阻塞狀態(tài)的可能。
t1.join()調(diào)用后,運(yùn)行狀態(tài)線程放出執(zhí)行權(quán),作為t1的后續(xù)線程,等待t1結(jié)束。也就是說至少得等t1線程run完畢,才可能進(jìn)入運(yùn)行狀態(tài)來執(zhí)行,可不是說t1執(zhí)行完,一定馬上就是調(diào)用t1.join()的線程馬上進(jìn)入可運(yùn)行行狀態(tài)。只有操作系統(tǒng)有權(quán)利決定誰進(jìn)入運(yùn)行狀態(tài)。
join的實(shí)質(zhì)就是讓兩個(gè)線程和二為一,串行。
t1.join();執(zhí)行這條語句現(xiàn)場(chǎng)是被保護(hù)起來的。t1結(jié)束,調(diào)用線程有機(jī)會(huì)運(yùn)行時(shí),會(huì)從上次的位置繼續(xù)運(yùn)行。














































線程優(yōu)先級(jí),setPriotity(1--100),數(shù)越大,優(yōu)先級(jí)越高。
開發(fā)中不提倡自省設(shè)置優(yōu)先級(jí),操作系統(tǒng)可能忽略優(yōu)先級(jí),不具有跨平臺(tái)性(兩方面,可運(yùn)行,執(zhí)行效果一致),因?yàn)檫@種方式很粗略。
static void yield(),運(yùn)行狀態(tài)的線程(當(dāng)前線程),調(diào)用yield方法,馬上交出執(zhí)行權(quán)。回到可運(yùn)行狀態(tài)。
=============================
Thread對(duì)象有個(gè)run方法,當(dāng)start()時(shí),Thread進(jìn)行系統(tǒng)級(jí)調(diào)用,系統(tǒng)分配一個(gè)線程空間,此時(shí)對(duì)象可以獲得CPU時(shí)間片,一個(gè)順序執(zhí)行流程可以獨(dú)立運(yùn)行,線程結(jié)束,對(duì)象還在,只是系統(tǒng)回收線程。
=============================
兩個(gè)線程同時(shí)的資源,稱為臨界資源,會(huì)有沖突。
堆棧數(shù)據(jù)結(jié)構(gòu),有一個(gè)char[]和一個(gè)index(表示實(shí)際長(zhǎng)度,也表示下一個(gè)要插入元素的位置)。
一個(gè)push操作,有兩個(gè)核心操作(加元素,修改index)。都執(zhí)行和都沒執(zhí)行,沒有問題。
但假設(shè)一個(gè)線程做了一個(gè)步,就交出執(zhí)行權(quán),別的線程,執(zhí)行同樣的代碼,會(huì)造成數(shù)據(jù)不一致。
數(shù)據(jù)完整性也是一個(gè)要在開發(fā)中注意的地方。
------------------------------------------
所以為了保證數(shù)據(jù)安全,要給數(shù)據(jù)加鎖。
一個(gè)Java對(duì)象,不僅有屬性和方法,還有別的東西。
任何一個(gè)對(duì)象,都有一個(gè)monitor,互斥鎖標(biāo)記,可以交給一個(gè)線程。
只有拿到這個(gè)對(duì)象互斥鎖標(biāo)記的線程,才能訪問這個(gè)對(duì)象。
synchronized可以修飾方法和代碼塊。
synchronized(obj){
obj.setValue(123);
}
不是每個(gè)線程都能進(jìn)入這段代碼塊,只有拿到鎖標(biāo)記的線程才能進(jìn)入執(zhí)行完,釋放鎖標(biāo)記,給下一個(gè)線程。
記住,鎖標(biāo)記是對(duì)對(duì)象來說的,鎖的是對(duì)象。
當(dāng)synchronized標(biāo)識(shí)方法時(shí),那么就是鎖當(dāng)前對(duì)象。































































































注意此代碼中,Stack這個(gè)臨界資源類中的push方法中,有一個(gè)Thread.sleep,它讓當(dāng)前進(jìn)程阻塞,也就是讓擁有Stack對(duì)象s鎖標(biāo)記的線程阻塞,但這時(shí)它并不釋放鎖標(biāo)記。
所以Synchronized使用是有代價(jià)的,犧牲效率換數(shù)據(jù)安全,要控制synchronized代碼塊,主要是數(shù)據(jù)寫,修改做同步限制,讀就不用了。
還有一點(diǎn)要注意:synchronized不能繼承, 父類的方法是synchronized,那么其子類重載方法中就不會(huì)繼承“同步”。
一個(gè)線程可以擁有很多對(duì)象鎖標(biāo)記,但一個(gè)對(duì)象的鎖標(biāo)記只能給一個(gè)線程。
等待鎖標(biāo)記的線程,進(jìn)入該對(duì)象的鎖池。
每個(gè)對(duì)象都有一個(gè)空間,鎖池,里面都是等待拿到該對(duì)象的鎖標(biāo)記的線程。
當(dāng)然還是操作系統(tǒng)來決定誰來獲得鎖標(biāo)記,在上一個(gè)鎖標(biāo)記釋放掉后。
死鎖,線程A拿到resourceA標(biāo)記,去請(qǐng)求resourceB;線程B拿到resourceB標(biāo)記,去請(qǐng)求resourceA;
線程間通訊機(jī)制->協(xié)調(diào)機(jī)制
一個(gè)對(duì)象不僅有鎖和鎖池,另外還有一個(gè)空間[等待隊(duì)列]。
synchronized(路南){
想要獲得路北資源的線程,調(diào)用路南.wait();將自己的所有鎖標(biāo)記都釋放。以便其他線程滿足條件運(yùn)行程序后,自己也就可以正常通過了。
}
調(diào)用obj.wait(),表示某一個(gè)線程釋放所有鎖標(biāo)記并進(jìn)入obj這個(gè)對(duì)象的等待隊(duì)列。
等待隊(duì)列也是阻塞狀態(tài)。一個(gè)線程調(diào)用obj對(duì)象的notify(),會(huì)通知等待隊(duì)列中的一個(gè)線程可以出來,notifyAll()是通知所有線程。











































































































































上面為經(jīng)典的生產(chǎn)者消費(fèi)者問題,生產(chǎn)者使用SyncStack的push方法,消費(fèi)者使用pop方法。
push方法:
while (index==data.length) {
try{
this.wait();//<-----------釋放所有鎖標(biāo)記,阻塞現(xiàn)場(chǎng)保留
}
catch (InterruptedException e){}
}
如果貨架滿了,生產(chǎn)者即使擁有鎖標(biāo)記,也不能再生產(chǎn)商品了,必須wait()。等待消費(fèi)者消費(fèi)物品,否則永遠(yuǎn)不會(huì)從SyncStack對(duì)象的等待隊(duì)列中出來。
等待通知,何時(shí)通知呢?
public synchronized char pop() {
while (index==0) {
try{
this.wait();
}
catch (InterruptedException e){}
}
index--;
char c=data[index];
data[index]=' ';
System.out.println("Char "+c+" Poped from Stack");
for(int k=0;k<data.length;k++) System.out.print(data[k]);
System.out.println();
this.notifyAll(); //<-------------所有等待隊(duì)列中的生產(chǎn)者都出了隊(duì)列,因?yàn)闆]有鎖標(biāo)記,只能進(jìn)入鎖池。
return c;
}
為什么判斷是一個(gè)while循環(huán),而不是一個(gè)if呢?
注意:我們假設(shè)一種情形:
1) 有十個(gè)生產(chǎn)者線程,貨架已經(jīng)滿了,10生產(chǎn)者依次獲得鎖標(biāo)記,依次都調(diào)用this.wait(),都進(jìn)入同一個(gè)SyncStack對(duì)象的等待隊(duì)列,10個(gè)進(jìn)程阻塞住,
2) 有一個(gè)消費(fèi)者線程獲得該SyncStack對(duì)象鎖標(biāo)記,一個(gè)消費(fèi)者消費(fèi)一個(gè),執(zhí)行完消費(fèi),調(diào)用this.notifyAll()
釋放鎖標(biāo)記。
3) 剛才消費(fèi)者調(diào)用SyncStack對(duì)象的notifyAll()后,10個(gè)線程都出來了,準(zhǔn)備生產(chǎn)商品,全部進(jìn)入鎖池。
這十個(gè)線程的代碼現(xiàn)場(chǎng),還在wait()這個(gè)函數(shù)調(diào)用后面,也就是一旦或者鎖標(biāo)記,要繼續(xù)從這里執(zhí)行。
this.wait();//從這一句的后面繼續(xù)執(zhí)行。
4) 但如果有一個(gè)生產(chǎn)者push的話,貨架已經(jīng)就滿了,但這時(shí)還有9個(gè)在鎖池中,依次獲得鎖標(biāo)記,但由于是while需要再次判斷是否貨架滿不滿,才能繼續(xù)前行進(jìn)行生產(chǎn)。如果是if,就會(huì)直接push,數(shù)組越界。
===================================
釋放鎖標(biāo)記只有兩種途徑,代碼執(zhí)行完,wait()
讓線程結(jié)束,就是想辦法讓run方法結(jié)束。
注意下面的bStop,標(biāo)志位,可以在線程進(jìn)入wait狀態(tài)時(shí),對(duì)某一線程調(diào)用interrupt(),線程拋出InterruptedException,然后根據(jù)標(biāo)志位,方法返回。


































































Exc:
AB1CD2....
數(shù)字與字母依次打印。用線程完成。





































































































