JAVA多線程學習筆記整理

首先回顧一下進程(Process)和線程(Thread)的區(qū)別:

進程:每個進程都有獨立的代碼和數(shù)據空間(進程上下文)鲸拥,進程間的切換會有較大的開銷,一個進程包含1--n個線程羽杰。(進程是資源分配的最小單位)

線程:同一類線程共享代碼和數(shù)據空間日缨,每個線程有獨立的運行棧和程序計數(shù)器(PC)力喷,線程切換開銷小。(線程是cpu調度的最小單位)

線程和進程一樣分為五個階段:創(chuàng)建僻焚、就緒、運行膝擂、阻塞虑啤、終止。

一架馋、基本概念

Java支持多線程狞山,也就是一個程序里面可以有多個并發(fā)運行的線程,在多線程模型中叉寂,多個線程共存于同一塊內存中萍启,且共享資源。CPU的時間被劃分為多個片段屏鳍,每個線程都分配有限的時間片來處理任務勘纯。至于你為什么覺得它們在并行運行,那是因為你的能力讓你看到的是這樣的钓瞭,切換速度賊快所以你感覺不到驳遵。

多線程的特點:

多個線程在運行時,系統(tǒng)在各個線程間進行切換山涡;

由于多個線程在同一塊內存里面堤结,線程之間通信非常容易搏讶;

Java將線程當做對象來管理。(通過類Thread或者接口Runable)

根據優(yōu)先級確定線程間切換的順序霍殴。優(yōu)先級是1~10的整數(shù)媒惕;

優(yōu)先級是線程之間的相對關系,只有一個線程的時候無關緊要来庭;

由于位于同一塊內存妒蔚,共享資源,所以可能產生沖突月弛,可以采用synchronized關鍵字協(xié)調資源肴盏,實現(xiàn)線程同步。

二帽衙、用線程類實現(xiàn)多線程

我們之前寫的程序都是單線程的菜皂,也就是main函數(shù)那一個線程(實際上也是多線程,因為還有一個垃圾回收線程厉萝,是系統(tǒng)的)恍飘;

要想實現(xiàn)多線程,就必須通過實例化一個Thread線程類的對象或者實現(xiàn)Runable接口谴垫;

例如:繼承Thread類章母,然后實例化線程類

classPrimeThreadextendsThread{//繼承Thread類,成為線程類longminPrime;PrimeThread(longminPrime){this.minPrime=minPrime;}publicvoidrun(){// compute primes larger than minPrime//將實際執(zhí)行代碼寫到run()方法中(重寫Thread中的run()方法)...}}Classtest{publicstaticvoidmian(String[]args){PrimeThreadp=newPrimeThread(143);//實例化一個線程類的對象p.start();//通過start()方法啟動線程}}

實現(xiàn)Runable接口

classPrimeRunimplementsRunnable{//實現(xiàn)Runable接口longminPrime;PrimeRun(longminPrime){this.minPrime=minPrime;}publicvoidrun(){//實現(xiàn)run()方法// compute primes larger than minPrime//將實際執(zhí)行代碼寫到run()方法中...}}Classtest{publicstaticvoidmian(String[]args){PrimeRunp=newPrimeRun(143);//創(chuàng)建一個線程對象newThread(p).start();//這里實際有兩部分翩剪,//先將p作為參數(shù)傳給Thread類的構造函數(shù)//然后調用線程類的start方法啟動線程}}

start()方法會自動調用run()方法乳怎,啟動線程。啟動線程只能用start()而不能直接用run();

Thread類的run()方法具有public屬性前弯,所以繼承Thread類的子類在重寫(覆蓋)該方法的時候必須也帶上public屬性蚪缀,否則就是方法的重載了∷〕觯可以在方法上一行加上“@Override”來判斷是不是在重寫父類的方法询枚;

Runable接口中的run()方法也有public屬性,所以和上面的一樣剃根,實現(xiàn)該接口類的run()方法也要帶上public哩盲;

如果一個類繼承Thread,則不適合資源共享狈醉。但是如果實現(xiàn)了Runable接口的話廉油,則很容易的實現(xiàn)資源共享。

實現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:

1)適合多個相同的程序代碼的線程去處理同一個資源苗傅;

2)可以避免java中的單繼承的限制抒线;

3)增加程序的健壯性,代碼可以被多個線程共享渣慕,代碼和數(shù)據獨立嘶炭;

4)線程池只能放入實現(xiàn)Runable或callable類線程抱慌,不能直接放入繼承Thread的類。

在java中所有的線程都是同時啟動的眨猎,至于什么時候抑进,哪個先執(zhí)行,完全看誰先得到CPU的資源睡陪。

三寺渗、線程的基本操作

1.啟動——start()

Threadt=newThread();//實例化一個線程類t.start;//調用start()啟動

2.睡眠——sleep()

try{Thread.sleep(1000);//括號里面表示毫秒,1000毫秒=1秒}catch(InterruptedExceptione){e.printStackTrace();}

3.停止

系統(tǒng)提供的stop()方法不建議使用了

所以我們一般采用在run()方法中加入while循環(huán)的形式兰迫,采用布爾型標記控制循環(huán)的停止與運行信殊,基本形式如下:

publicclassInterruptedextendsThread{privateblooeanisContinue=false;publicvoidrun(){while(true){//...if(!isContinue){break;//如果是真的,就終止循環(huán)汁果,停止run()的執(zhí)行}}}publicvoidsetContinue(blooeanpar){this.isContinue=par;//設置實例變量isContinue的值}}

四涡拘、資源的協(xié)調與同步

1.線程調度模型

線程分配處理器使用權的過程,主要有兩種:協(xié)同式線程調度(Cooperative Thread-Scheduling)和搶占式線程調度(Preemptive Threads-Scheduling)

Java使用的是搶占式線程調度据德,通過設置線程優(yōu)先級“建議”系統(tǒng)給有些線程多分配時間鳄乏,不同操作系統(tǒng)對于優(yōu)先級的反應并不一定相同(比如Windows操作系統(tǒng)優(yōu)先級7種,Java有10種晋控,那么有些優(yōu)先級映射到系統(tǒng)原生線程后就是一樣的)

搶占模式下汞窗,操作系統(tǒng)負責分配CPU時間給各個進程姓赤,一旦當前的進程使用完分配給自己的CPU時間赡译,操作系統(tǒng)將決定下一個占用CPU時間的是哪一個線程。因此操作系統(tǒng)將定期的中斷當前正在執(zhí)行的線程不铆,將CPU分配給在等待隊列的下一個線程蝌焚。所以任何一個線程都不能獨占CPU。每個線程占用CPU的時間取決于進程和操作系統(tǒng)誓斥。進程分配給每個線程的時間很短,以至于我們感覺所有的線程是同時執(zhí)行的只洒。實際上,系統(tǒng)運行每個進程的時間很短(可能就幾毫秒),然后調度其它的線程。它同時維持著所有的線程和循環(huán),分配很少量的CPU時間給線程劳坑。線程的的切換和調度是如此之快,以至于感覺是所有的線程是同步執(zhí)行的毕谴。

非搶占的調度模式下,每個線程可以需要CPU多少時間就占用CPU多少時間。在這種調度方式下,可能一個執(zhí)行時間很長的線程使得其它所有需要CPU的線程”餓死”距芬。在處理機空閑涝开,即該進程沒有使用CPU時,系統(tǒng)可以允許其他的進程暫時使用CPU框仔。占用CPU的線程擁有對CPU的控制權舀武,只有它自己主動釋放CPU時,其他的線程才可以使用CPU离斩。

2.優(yōu)先級高的線程讓出CPU的情況

調用yield()方法退出银舱;

不再是可運行的(處于消亡或者阻塞狀態(tài))瘪匿;

被其他優(yōu)先級更高的線程替代(具有更高的優(yōu)先級的線程可能已經休眠了指定的時間段,或者它的I/O操作已經結束寻馏,或者調用了resume()或者notify()方法)棋弥;

這時候,調度程序會選擇一個在可運行線程中優(yōu)先級最高的線程運行诚欠,如果多個線程具有相同的優(yōu)先級嘁锯,那么它們會輪流調度。

3.資源沖突

多個線程同時運行會提高程序的執(zhí)行效率聂薪,但是由于它們在同一個內存區(qū)域家乘,共享一組資源,在運行的時候可能會引起死鎖(Deadlock)藏澳。

就像這樣仁锯,T1占有R1請求R2,T2占有R2請求R1……結果誰都得不到翔悠,誰又放不開业崖,然后就GG了。

上面也有說到對于只有一個CPU的系統(tǒng)來說蓄愁,“多個線程同時運行”相對的双炕,每時每刻都只有一個線程在運行,只是運行速度和切換速度特別快撮抓。

舉個資源沖突的栗子:

classUserThread{voidPlay(intn){System.out.println("運行線程 NO:"+n);try{Thread.sleep(3);// 采用睡眠模擬程序的運行}catch(InterruptedExceptione){System.out.println("線程異常妇斤,NO:"+n);}System.out.println("結束線程 NO:"+n);}}classUserMultThreadimplementsRunnable{//實現(xiàn)Runable接口成為線程類UserThreadUserObj;intnum;UserMultThread(UserThreado,intn){UserObj=o;num=n;}publicvoidrun(){UserObj.Play(num);}}publicclassmultTheadTwo{publicstaticvoidmain(Stringargs[]){UserThreadObj=newUserThread();// 定義對象ObjThreadt1=newThread(newUserMultThread(Obj,1));// 采用Obj產生三個線程對象Threadt2=newThread(newUserMultThread(Obj,2));Threadt3=newThread(newUserMultThread(Obj,3));t1.start();//分別啟動三個線程t2.start();t3.start();}}

輸出結果為:

運行線程 NO:2

運行線程 NO:1

運行線程 NO:3

結束線程 NO:2

結束線程 NO:1

結束線程 NO:3

這只是一種可能的結果,因為系統(tǒng)對于三個線程的處理情況是不確定的丹拯,但是可以看出來一個線程的play()方法還沒有執(zhí)行完站超,就被另一個線程搶了過去,說明這其中三個線程就對play()這個產生了競爭乖酬。

4.同步方法

上述問題可以通過關鍵字synchronized關鍵字實現(xiàn)線程的同步來解決死相。

當一個對象使用了synchronized關鍵字后,這個對象就被鎖定或者說進入了監(jiān)視器(monitor)咬像,這樣可以保證每時每刻都只有一個線程進入監(jiān)視器來訪問被鎖定的對象算撮,其它線程就被掛起直到之前的線程退出監(jiān)視器。

Java中每一個對象都與監(jiān)視器相連县昂,但是如果不使用同步關鍵字肮柜,監(jiān)視器就不會真的被分配。

實現(xiàn)同步有兩種方法:

1.鎖定沖突的對象

synchronized(ObjRef){Block//需要同步執(zhí)行的語句體}

例如將上面的例子中的run()方法里面的UserObj鎖定

publicvoidrun(){synchronized(UserObj){UserObj.Play(num);}}

這樣就實現(xiàn)了同步七芭,執(zhí)行一次結果如下:

運行線程 NO:2

結束線程 NO:2

運行線程 NO:1

結束線程 NO:1

運行線程 NO:3

結束線程 NO:3

2.鎖定沖突的方法

synchronized方法的定義

例如將上面的play()方法鎖定

classUserThread{synchronizedvoidPlay(intn){System.out.println("運行線程 NO:"+n);try{Thread.sleep(3);// 采用睡眠模擬程序的運行}catch(InterruptedExceptione){System.out.println("線程異常素挽,NO:"+n);}System.out.println("結束線程 NO:"+n);}}

當一個線程在執(zhí)行play()的時候,另一個線程如果也要執(zhí)行這方法狸驳,就會被阻塞预明;

所以輸出一次結果如下:

運行線程 NO:2

結束線程 NO:2

運行線程 NO:3

結束線程 NO:3

運行線程 NO:1

結束線程 NO:1

對run()方法無法加鎖缩赛,編譯可通過但是無法避免沖突;

無法對構造函數(shù)加鎖撰糠,會出現(xiàn)語法錯誤酥馍;

五、線程間通信

Java實現(xiàn)多線程通信是通過系統(tǒng)方法實現(xiàn)的阅酪,只要是wait()旨袒、notify()和notifyAll()方法

wait()方法

wait()方法使得當前線程必須要等待,等到另外一個線程調用notify()或者notifyAll()方法术辐。

線程調用wait()方法砚尽,釋放它對鎖的擁有權,然后等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法)辉词,這樣它才能重新獲得鎖的擁有權和恢復執(zhí)行必孤。

注意:

要確保調用wait()方法的時候擁有鎖,即wait()方法的調用必須放在synchronized方法或synchronized塊中瑞躺;

另一個會導致線程暫停的方法:Thread.sleep()敷搪,它會導致線程睡眠指定的毫秒數(shù),但線程在睡眠的過程中不會釋放掉對象的鎖幢哨。

notify()方法

notify()方法通知等待監(jiān)視器的線程赡勘,該對象的狀態(tài)已經改變。會喚醒一個等待的線程捞镰。

注意:

如果多個線程在等待闸与,它們中的一個將會選擇被喚醒。這種選擇是隨意的曼振,和具體實現(xiàn)有關几迄;

被喚醒的線程是不能被執(zhí)行的,需要等到當前線程放棄這個對象的鎖冰评;

被喚醒的線程將和其他線程以通常的方式進行競爭,來獲得對象的鎖木羹。也就是說甲雅,被喚醒的線程并沒有什么優(yōu)先權,也沒有什么劣勢坑填,對象的下一個線程還是需要通過一般性的競爭抛人,也就是看優(yōu)先級;

notify()方法應該是被擁有對象的鎖的線程所調用脐瑰。也就是和wait()方法一樣妖枚,必須放在synchronized方法或synchronized塊中。

notifyAll()方法

notifyAll()方法會喚醒從同一個監(jiān)視器中用wait()方法退出的所有線程苍在,使它們按照優(yōu)先級順序重新排隊

線程調度比較復雜绝页,編寫程序應該遵守以下規(guī)則:

如果有多個線程訪問同一個對象荠商,就要將執(zhí)行修改操作的方法聲明為synchronized;

如果線程必須等待某個對象的狀態(tài)续誉,則應該在同步方法內調用wait()方法莱没;

每當一個方法改變了某個對象的狀態(tài),就應該調用notify()方法酷鸦。這樣饰躲,等待線程就有機會檢查環(huán)境是否發(fā)生了變化。

在這篇博客看到一張不錯的說線程狀態(tài)的圖

感謝 @林炳文Evankaka:http://blog.csdn.net/evankaka

1臼隔、新建狀態(tài)(New):新創(chuàng)建了一個線程對象嘹裂。

2、就緒狀態(tài)(Runnable):線程對象創(chuàng)建后摔握,其他線程調用了該對象的start()方法焦蘑。該狀態(tài)的線程位于可運行線程池中,變得可運行盒发,等待獲取CPU的使用權例嘱。

3、運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU宁舰,執(zhí)行程序代碼拼卵。

4、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權蛮艰,暫時停止運行腋腮。直到線程進入就緒狀態(tài),才有機會轉到運行狀態(tài)壤蚜。阻塞的情況分三種:

等待阻塞:運行的線程執(zhí)行wait()方法即寡,JVM會把該線程放入等待池中。(wait()會釋放持有的鎖)

同步阻塞:運行的線程在獲取對象的同步鎖時袜刷,若該同步鎖被別的線程占用聪富,則JVM會把該線程放入鎖池中。

其他阻塞:運行的線程執(zhí)行sleep()或join()方法著蟹,或者發(fā)出了I/O請求時墩蔓,JVM會把該線程置為阻塞狀態(tài)。當sleep()狀態(tài)超時萧豆、join()等待線程終止或者超時奸披、或者I/O處理完畢時,線程重新轉入就緒狀態(tài)涮雷。(注意,sleep是不會釋放持有的鎖,,上面我已經說過了)

5阵面、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結束生命周期。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末样刷,一起剝皮案震驚了整個濱河市仑扑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颂斜,老刑警劉巖夫壁,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異沃疮,居然都是意外死亡盒让,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門司蔬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邑茄,“玉大人,你說我怎么就攤上這事俊啼》温疲” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵授帕,是天一觀的道長同木。 經常有香客問我,道長跛十,這世上最難降的妖魔是什么彤路? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任扬卷,我火速辦了婚禮葡公,結果婚禮上结澄,老公的妹妹穿的比我還像新娘灾挨。我一直安慰自己,他們只是感情好萍膛,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布砚哆。 她就那樣靜靜地躺著本股,像睡著了一般惊来。 火紅的嫁衣襯著肌膚如雪丽涩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天唁盏,我揣著相機與錄音内狸,去河邊找鬼。 笑死厘擂,一個胖子當著我的面吹牛,可吹牛的內容都是我干的锰瘸。 我是一名探鬼主播刽严,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舞萄?” 一聲冷哼從身側響起眨补,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倒脓,沒想到半個月后撑螺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡崎弃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年甘晤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饲做。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡线婚,死狀恐怖,靈堂內的尸體忽然破棺而出盆均,到底是詐尸還是另有隱情塞弊,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布泪姨,位于F島的核電站游沿,受9級特大地震影響,放射性物質發(fā)生泄漏肮砾。R本人自食惡果不足惜诀黍,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唇敞。 院中可真熱鬧蔗草,春花似錦、人聲如沸疆柔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旷档。三九已至模叙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鞋屈,已是汗流浹背范咨。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留厂庇,地道東北人渠啊。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像权旷,于是被迫代替她去往敵國和親替蛉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內容

  • 本文包括:1、Listener簡介2躲查、Servlet監(jiān)聽器3它浅、監(jiān)聽三個域對象創(chuàng)建和銷毀的事件監(jiān)聽器4、監(jiān)聽三個域對...
    廖少少閱讀 6,079評論 6 28
  • IOC 控制反轉容器控制程序對象之間的關系镣煮,而不是傳統(tǒng)實現(xiàn)中姐霍,有程序代碼之間控制,又名依賴注入典唇。All 類的創(chuàng)建镊折,...
    irckwk1閱讀 945評論 0 0
  • 一腌乡、JSP 1. jsp的介紹 JSP全名為Java Server Pages,中文名叫java服務器頁面夜牡,本質是...
    圣賢與無賴閱讀 533評論 1 0
  • 憶 世上酸甜苦辣咸与纽, 人不癲狂不少年。 常想兒時荒唐事塘装, 歡樂伴我入夢眠急迂。
    老爸的雜拌兒糖閱讀 294評論 8 18
  • 楊冪和劉愷威 12 月 22 日發(fā)表聲明“兩位藝人因私事占用媒體公共資源僚碎,并給大家?guī)砑姅_,向所有人致以最誠摯的歉...
    隱蟬閱讀 323評論 0 0