????01-多線程(概述)
? ? ? ? 接下來(lái),我們來(lái)說(shuō)一說(shuō)Java中特有的一個(gè)知識(shí)技術(shù):多線程。
? ? ? ? 在說(shuō)線程這個(gè)概念之前呢,需要說(shuō)一個(gè)更顯而易見(jiàn)的概念:進(jìn)程。
? ? ? ? 何為進(jìn)程呢?進(jìn)程就是正在進(jìn)行中的程序。
? ? ? ? 進(jìn)程可以同時(shí)開(kāi)啟,其實(shí)就是cpu在對(duì)它們執(zhí)行。
? ? ? ? 不過(guò),雖然它們看起來(lái)像是同時(shí)在執(zhí)行,其實(shí),cpu在某一時(shí)刻只能執(zhí)行某一個(gè)程序,它只是在做著超快的切換,咻咻咻咻~才導(dǎo)致我們看到的是各個(gè)程序在同時(shí)執(zhí)行。
? ? ? ? 再講個(gè)迅雷的例子。是不是有時(shí)候會(huì)有多條下載請(qǐng)求呀?一個(gè)進(jìn)程,里面可能會(huì)有多條執(zhí)行路徑。什么意思呢?在我們進(jìn)行迅雷下載的時(shí)候呢,大家都知道迅雷可以有多條下載路徑,比如100M的文件,迅雷可能會(huì)分成五個(gè)部分,一個(gè)部分一個(gè)部分的同時(shí)發(fā)送請(qǐng)求到服務(wù)端去下載數(shù)據(jù)。我們想一想,如果我們下載是用一個(gè)路徑,就是先下載第一個(gè)數(shù)據(jù),然后一個(gè)一個(gè)下載,一直下載到第100個(gè)數(shù)據(jù)。可是如果是五條路徑下載,五個(gè)人同時(shí)在搬東西,是不是就比一個(gè)人快多啦?而且這五個(gè)人都是在這一個(gè)進(jìn)程中哦,這其中的每一個(gè)人,就叫做線程。線程是進(jìn)程中的內(nèi)容,每一個(gè)應(yīng)用程序,里面都至少有一個(gè)線程。
? ? ? ? 線程有一個(gè)特點(diǎn),它是程序中的控制單元,或者叫執(zhí)行路徑。
? ? ? ? 總結(jié)一下:
? ? ? ? 進(jìn)程:是一個(gè)正在執(zhí)行中的程序。
? ? ? ? ? ? ? ? ?每一個(gè)進(jìn)程執(zhí)行都有一個(gè)執(zhí)行順序,該順序是一個(gè)執(zhí)行路徑,或者叫一個(gè)控制單元。
? ? ? ? 每個(gè)程序執(zhí)行,都需要啟動(dòng)內(nèi)存空間,而進(jìn)程其實(shí)就是用來(lái)標(biāo)識(shí)內(nèi)存空間的,它用來(lái)封裝里面的那些控制單元。
? ? ? ? 線程:就是進(jìn)程中的一個(gè)獨(dú)立的控制單元。
? ? ? ? ? ? ? ? ? 線程在控制著進(jìn)程的執(zhí)行。
? ? ? ? ? ? ? ? ? 一個(gè)進(jìn)程中至少有一個(gè)線程。
? ? ? ? 以Java為例,編譯運(yùn)行的時(shí)候,它有兩個(gè)進(jìn)程:編譯進(jìn)程和運(yùn)行進(jìn)程。
? ? ? ? 我們重點(diǎn)說(shuō)一下運(yùn)行進(jìn)程。
? ? ? ? Java虛擬機(jī)啟動(dòng)的時(shí)候會(huì)有一個(gè)進(jìn)程java.exe。該進(jìn)程中至少有一個(gè)線程來(lái)負(fù)責(zé)Java程序的執(zhí)行,而且這個(gè)線程運(yùn)行的代碼存在于main方法中。該線程稱之為主線程。
? ? ? ? 其實(shí),很多書(shū)里面都在說(shuō),這個(gè)程序在啟動(dòng)執(zhí)行的時(shí)候是單線程程序,因?yàn)檫€沒(méi)有其他線程存在呢。這樣說(shuō)理解也是對(duì)的。
? ? ? ? 擴(kuò)展:
? ? ? ? 但是,如果你深究一下虛擬機(jī)的話,其實(shí)不是單線程。虛擬機(jī)啟動(dòng)的時(shí)候,它就是多線程。為什么呢?另外的線程是誰(shuí)呢?
? ? ? ? 試想一下,(這個(gè)只作為了解喔,因?yàn)檎f(shuō)這個(gè)程序是單線程也沒(méi)問(wèn)題噠)程序在運(yùn)行的時(shí)候,是不是要建立對(duì)象,調(diào)用方法,這個(gè)時(shí)候是主線程在幫我們做這件事情,堆里面會(huì)產(chǎn)生很多對(duì)象,如果其中某一個(gè)對(duì)象不被使用了,它就會(huì)被垃圾回收機(jī)制回收了。主線程還在繼續(xù)執(zhí)行著其他對(duì)象中的操作,而這個(gè)對(duì)象就被干掉了,是不是在同時(shí)進(jìn)行吶?是滴!
? ? ? ? 這個(gè)時(shí)候就產(chǎn)生了一個(gè)問(wèn)題,主線程在繼續(xù)調(diào)用方法的同時(shí),另外的不被使用的對(duì)象就被垃圾回收機(jī)制回收了。所以這個(gè)時(shí)候虛擬機(jī)至少有兩個(gè)線程,一個(gè)是主線程,一個(gè)是垃圾回收的線程。
? ? ? ? 多線程存在的意義是什么呢?
? ? ? ? 多線程的出現(xiàn)呢,可以讓我們程序中的部分產(chǎn)生同時(shí)運(yùn)行的效果。而且在下載東西的時(shí)候,多線程下載還可以幫我們提高效率。
????02-多線程(創(chuàng)建線程-繼承Thread類)
? ? ? ? 那么,如何在我們的程序中自定一個(gè)控制單元,或者說(shuō),自定一個(gè)線程?
? ? ? ? Java中已經(jīng)為我們提供好了對(duì)這類事物的對(duì)象體現(xiàn)。
? ? ? ? 通過(guò)對(duì)API的查找,我們發(fā)現(xiàn),java的核心包java.lang包中就有一個(gè)對(duì)象叫做Thread,它就是程序中的執(zhí)行線程,就是用于描述控制單元這類事物的對(duì)象。
? ? ? ? 我們發(fā)現(xiàn)創(chuàng)建新執(zhí)行線程有兩種方法:
? ? ? ? 連范例都有啦,我們自己來(lái)寫(xiě)~
? ? ? ? 創(chuàng)建線程的第一種方式:繼承Thread類。? ? ? ?
? ? ? ? 第一步,繼承Thread類:
? ? ? ? 接下來(lái)我們看看Thread中的run方法是怎樣的~
? ? ? ? 第二部,重寫(xiě)run方法:
? ? ? ? 繼承完之后,我們接下來(lái)就要?jiǎng)?chuàng)建它的對(duì)象啦。注意哦,建立好一個(gè)對(duì)象,其實(shí)就是創(chuàng)建好一個(gè)線程。
? ? ? ? 創(chuàng)建好對(duì)象之后就要調(diào)用run方法啦。
? ? ? ? 我們看到范例中有一個(gè)start方法:
? ? ? ? 查一下start方法是干什么的~
? ? ? ? OK,寫(xiě)好啦:
? ??????
? ? ? ? 現(xiàn)在編譯運(yùn)行:
? ? ? ? demo run成功打印了。
? ???????總結(jié)一下,?創(chuàng)建線程的第一種方式:繼承Thread類的步驟為:
? ??????1,定義類繼承Thread。
? ? ? ? 2,復(fù)寫(xiě)Thread類中的run方法。
? ? ? ? 3,調(diào)用線程的start方法,該方法有兩個(gè)作用:?jiǎn)?dòng)線程,調(diào)用run方法。
? ? ? ? 冷靜下來(lái)思考一下,但是這跟我們以前調(diào)用方法的方式有什么區(qū)別呢?怎么看不出來(lái)呀?
? ? ? ? 接下來(lái)我們讓它多運(yùn)行一會(huì)兒,寫(xiě)了一個(gè)循環(huán)~
? ? ? ? 讓hello world也參與一下~
? ? ? ? 編譯運(yùn)行:
????????我們分析一下它的執(zhí)行路徑:?
? ? ? ? 所以我們?cè)诖蛴〗Y(jié)果的時(shí)候是交替打印的。
? ? ? ? 為什么是交替的呢?main線程和d線程它們倆不是同時(shí)執(zhí)行嗎?
? ? ? ? 跟你講哦,真實(shí)情況下是不可能的。
? ? ? ? windows本身是一個(gè)多任務(wù)操作系統(tǒng),看上去它確實(shí)在同時(shí)執(zhí)行,其實(shí)真正的情況是,cpu在某一時(shí)刻下,只能執(zhí)行一個(gè)程序。為什么看起來(lái)是同時(shí)執(zhí)行的呢?因?yàn)閏pu在這些程序之間做著快速的切換,切換的速度是相當(dāng)快的,快到你根本感覺(jué)不出來(lái)它在切換,所以你覺(jué)得它在同時(shí)執(zhí)行。
? ? ? ? 而進(jìn)程中真正在執(zhí)行的是線程,所以cpu在切換的是每一個(gè)進(jìn)程中的線程。而一個(gè)進(jìn)程中有多個(gè)線程的話,cpu也要做切換呢。(這也是機(jī)器中程序開(kāi)的越多越慢的原因)
? ? ? ? 我們現(xiàn)在就不說(shuō)多進(jìn)程啦,我們就說(shuō)其中一個(gè)進(jìn)程好啦。
? ? ? ? 在這個(gè)例子中,這一個(gè)進(jìn)程中,就已經(jīng)有多個(gè)線程啦。cpu執(zhí)行main一會(huì)兒,執(zhí)行d一會(huì)兒。那么這種情況,我們形象的把它稱之為,多個(gè)線程在搶劫cpu資源。這只是一種形象的說(shuō)法,其實(shí)是cpu說(shuō)了算。只是說(shuō)“搶”更形象一點(diǎn)兒~
? ? ? ? #Java小劇場(chǎng)
? ? ? ? cpu:執(zhí)行main一會(huì)兒,執(zhí)行main一會(huì)兒,好了好了,再執(zhí)行d一會(huì)兒,再執(zhí)行d一會(huì)兒,再執(zhí)行d一會(huì)兒,誒,好像都沒(méi)顧到main了,那我再執(zhí)行main一會(huì)兒好了。。。
? ? ? ? #
? ? ? ? 早期有一些病毒就是通過(guò)消耗cpu資源讓電腦死機(jī)的。它就是一直在搶用cpu資源,從而達(dá)到讓別人搶不著的效果。
? ? ? ? 再回到我們的例子中。
? ? ? ? 到這里main線程就結(jié)束啦,那這個(gè)進(jìn)程會(huì)結(jié)束嗎?不會(huì)。因?yàn)閐線程還沒(méi)有結(jié)束,只要d線程還在,這個(gè)進(jìn)程就存在。
? ? ? ? 現(xiàn)在有了雙核、多核處理器,就可以實(shí)現(xiàn)同時(shí)運(yùn)行了,這個(gè)cpu處理這個(gè),那個(gè)cpu處理那個(gè)。不過(guò)具體是怎么切換的,我們現(xiàn)在暫時(shí)控制不了,可以學(xué)一下多核編程玩一下~
? ? ? ? 雙核以后誰(shuí)是瓶頸??jī)?nèi)存。
? ? ? ? #Java小劇場(chǎng)
? ? ? ? 小楠老師在做一個(gè)一對(duì)三的高考沖刺班,她同時(shí)帶三個(gè)孩子,一會(huì)給這個(gè)輔導(dǎo),一會(huì)給那個(gè)輔導(dǎo),還算忙得過(guò)來(lái)。這個(gè)時(shí)候又有一個(gè)人報(bào)名了,小楠老師忙不過(guò)來(lái)了,于是就又請(qǐng)了一位老師,這位新老師來(lái)帶新報(bào)名的這個(gè)孩子。這樣,小楠老師和新老師可以同時(shí)帶四個(gè)孩子。
? ? ? ? 小楠老師的補(bǔ)習(xí)班空間不大,四個(gè)孩子就剛好坐滿啦。其實(shí)新老師也可以再帶兩個(gè)孩子呢,這樣小楠老師和新老師就可以同時(shí)帶六個(gè)孩子,達(dá)到最大的效率。可是因?yàn)檠a(bǔ)習(xí)班太小,不能容納更多孩子,小楠老師和新老師還是只能帶四個(gè)孩子。
? ? ? ? #
? ? ? ? 現(xiàn)在雙核之后,還有四核、八核,聽(tīng)起來(lái)好膩害哦。但是可能會(huì)出現(xiàn)cpu想處理數(shù)據(jù)但沒(méi)數(shù)據(jù)處理的情況。(沒(méi)有地方裝數(shù)據(jù)呀)
? ? ? ? 再回到剛剛的例子中。我們又多運(yùn)行了幾次,發(fā)現(xiàn)每次運(yùn)行打印的順序都不同。
? ? ? ? 因?yàn)槎鄠€(gè)線程都在獲取cpu的執(zhí)行權(quán)。cpu執(zhí)行到誰(shuí),誰(shuí)就運(yùn)行。
? ? ? ? 明確一點(diǎn),在某一個(gè)時(shí)刻,只能有一個(gè)程序在運(yùn)行。(多核除外)
? ? ? ? cpu在做著快速的切換,以達(dá)到看上去是同時(shí)運(yùn)行的效果。
? ? ? ? 我們可以形象的把多線程的運(yùn)行行為看做在互相搶奪cpu的執(zhí)行權(quán)。
? ? ? ? 這就是多線程的一個(gè)特性:隨機(jī)性。誰(shuí)搶到誰(shuí)執(zhí)行,至于執(zhí)行多長(zhǎng)時(shí)間,cpu說(shuō)的算。
? ? ? ? 當(dāng)然,也會(huì)出現(xiàn)這樣的情況,hello world執(zhí)行完了,才執(zhí)行demo run,或者demo run執(zhí)行完了,才想起來(lái)執(zhí)行hello world。這種情況是cpu切換得有點(diǎn)慢。哈哈~遲鈍的cpu~
? ? ? ? 再當(dāng)然,在沒(méi)有被特意控制的情況下,不會(huì)出現(xiàn)cpu把一個(gè)全部執(zhí)行完再執(zhí)行另一個(gè)的情況。 因?yàn)閏pu它在優(yōu)化資源,它得去快速的做著切換,才能實(shí)現(xiàn)同時(shí)運(yùn)行的效果,否則會(huì)出現(xiàn)一個(gè)程序在執(zhí)行,另一個(gè)程序執(zhí)行不了。
????04-多線程(線程練習(xí))
? ? ? ? 練習(xí):創(chuàng)建兩個(gè)線程,和主線程交替運(yùn)行。
? ? ? ? 運(yùn)行結(jié)果:
? ? ? ? 這樣寫(xiě)會(huì)怎樣呢:
? ? ? ? 還是只有一個(gè)主線程在執(zhí)行,另外兩個(gè)線程沒(méi)有開(kāi)啟。
? ? ? ? 主線程先打印one run,再打印two run,最后打印main。
? ? ? ? 這里我們的循環(huán)只有60次,所以主線程運(yùn)行一段程序的時(shí)候,其他程序等待時(shí)間不會(huì)太長(zhǎng),但是如果是6000、60000次呢?這個(gè)其他程序的等待時(shí)間就很長(zhǎng)啦。
? ? ? ? 所以,建立多個(gè)線程,多個(gè)程序就可以“同時(shí)”運(yùn)行~
????05-多線程(線程運(yùn)行狀態(tài))
? ? ? ? 線程的四種狀態(tài):
????06-多線程(獲取線程對(duì)象以及名稱)
? ? ? ? 我們來(lái)看看關(guān)于線程名稱的方法,有setName:
? ? ? ? getName:
? ? ? ? 我們用getName方法來(lái)獲取一下線程名稱。
? ? ? ? 依然用之前的那個(gè)例子~
? ? ? ? 編譯運(yùn)行:
? ? ? ? 我們發(fā)現(xiàn)有Thread-1和Thread-0兩個(gè)名字。
? ? ? ? 原來(lái)線程都有自己默認(rèn)的名稱。
? ? ? ? Thread-編號(hào) 該編號(hào)從0開(kāi)始。
? ? ? ? 我們也可以改變線程的名稱,用setName方法就好啦。
? ? ? ? 不過(guò)查一下Thread的構(gòu)造函數(shù),發(fā)現(xiàn)線程初始化的時(shí)候就可以有名稱啦!
? ? ? ? 我們調(diào)用一下父類的構(gòu)造方法:
? ? ? ? 現(xiàn)在在創(chuàng)建它們的時(shí)候就給它們起一下名字:
? ? ? ? 編譯運(yùn)行:
? ? ? ? 自定義線程名稱成功啦~
? ? ? ? 另外,Thread類還為我們提供了一個(gè)方法:currentThread()。
? ? ? ? 這個(gè)方法是靜態(tài)的,說(shuō)明這個(gè)對(duì)象沒(méi)有訪問(wèn)到對(duì)象的特有數(shù)據(jù),用類名訪問(wèn)就可以啦。
? ? ? ? 我們使用這個(gè)方法,來(lái)獲得當(dāng)前線程的名稱:
? ? ? ? 獲取成功~(運(yùn)行截圖略,就和this.getName()運(yùn)行結(jié)果一樣)
? ? ? ? 既然兩種方法運(yùn)行結(jié)果一樣,它們有什么不同呢?
? ? ? ? 試一下:
? ? ? ? 運(yùn)行:
? ? ? ? 運(yùn)行結(jié)果是true哦。所以它倆是一樣噠。
? ? ? ? 那用this調(diào)用就行了呀,多簡(jiǎn)單,用currentThread那么長(zhǎng),那么麻煩。
? ? ? ? 不是的。this調(diào)用的方式并不通用,只有在類的對(duì)象調(diào)用類中方法時(shí)才可以用則中方式調(diào)用。而currentThread是標(biāo)準(zhǔn)通用方法,無(wú)論誰(shuí)調(diào)用都可以用~
? ? ? ? 我們來(lái)復(fù)習(xí)一下:
? ? ? ? static Thread currentThread():獲取當(dāng)前線程對(duì)象。
? ? ? ? getName():獲取線程名稱。
? ? ? ? 設(shè)置線程名稱:setName()或者構(gòu)造函數(shù)。
? ? ? ? 還有一個(gè)小問(wèn)題:
? ? ? ? Thread-0和Thread-1“同時(shí)”運(yùn)行的時(shí)候,用的x是同一個(gè)嗎?
? ? ? ? 不是的。
? ? ? ? Thread-0建立的時(shí)候,內(nèi)存中會(huì)為它分配一塊內(nèi)存空間,其中有一塊叫x;Thread-1建立的時(shí)候,內(nèi)存也會(huì)為它分配另一塊內(nèi)存空間,其中也有一塊叫x。多個(gè)線程“同時(shí)”運(yùn)行的時(shí)候,注意局部變量是每個(gè)內(nèi)存空間中都有一份哦。
????07-多線程(售票的例子)
? ? ? ? 接下來(lái),我們用一個(gè)事例來(lái)對(duì)第二種方法進(jìn)行闡述。
? ? ? ? 需求:簡(jiǎn)單的賣票程序。
? ? ? ? 多個(gè)窗口同時(shí)賣票。
? ? ? ? 現(xiàn)在4個(gè)窗口同時(shí)賣票:
? ? ? ? 運(yùn)行一下,發(fā)現(xiàn)分不清都是哪個(gè)窗口賣的:
? ? ? ? 打印一下線程的名字:
? ? ? ? 運(yùn)行,我們發(fā)現(xiàn)了一個(gè)問(wèn)題:
? ? ? ? 1、2、3、4號(hào)窗口都賣了1號(hào)票。一節(jié)車廂就100個(gè)座,可是現(xiàn)在賣出了400個(gè)座。
? ? ? ? 問(wèn)題在于,創(chuàng)建一個(gè)對(duì)象,里面就有100張票。
? ? ? ? 我們的解決方式:讓4個(gè)對(duì)象共享100張票。
? ? ? ? 該用靜態(tài)啦!
? ? ? ? 運(yùn)行:
? ? ? ? OK啦。沒(méi)有重復(fù)賣票的啦。
? ? ? ? 但是,我們一般是不是不定義靜態(tài)呀?因?yàn)樗纳芷谔L(zhǎng)啦!
? ? ? ? 那怎么解決呢?
? ? ? ? 用一個(gè)對(duì)象來(lái)賣100張票:
? ? ? ? 運(yùn)行:
? ? ? ? 是不是也賣完啦~
? ? ? ? 但是發(fā)現(xiàn)這些亂七八糟什么東西呀:
? ? ? ? 也就是說(shuō),線程已經(jīng)開(kāi)啟了,并且調(diào)用start函數(shù),從開(kāi)啟狀態(tài)進(jìn)入了運(yùn)行狀態(tài)。這個(gè)時(shí)候又調(diào)用了start函數(shù),又進(jìn)入運(yùn)行狀態(tài),這就是無(wú)效的呀。
? ? ? ? 該怎么解決呢?
? ? ? ? 快接著看下去~
????08-多線程(創(chuàng)建線程-實(shí)現(xiàn)Runnable接口)
? ? ? ? 解決這個(gè)問(wèn)題,我們就需要引入第二種創(chuàng)建線程的方式了。
? ? ? ? 我們來(lái)看一下Runnable接口:
? ? ? ? 這個(gè)接口里面非常的爽呀,就一個(gè)方法:
? ? ? ? 我們來(lái)跟著示例代碼來(lái)寫(xiě)~emmm...不太懂耶。
? ? ? ? 那慢慢一步一步分析著來(lái)~先讓Ticket類實(shí)現(xiàn)Runnable接口:
? ? ? ? 主函數(shù)中:
? ? ? ? 我們現(xiàn)在需要想辦法讓線程調(diào)用Ticket中的run,需要讓Ticket中的run和Thread創(chuàng)建的對(duì)象有關(guān)系!
? ? ? ? 我們需要在創(chuàng)建線程對(duì)象時(shí)就明確要運(yùn)行什么代碼。
? ? ? ? Thread有一個(gè)構(gòu)造方法,比較特殊:
? ? ? ? 它可以接收Runnable接口類型的對(duì)象。
? ? ? ? 所以這就是我們要實(shí)現(xiàn)Runnable接口的原因,因?yàn)門hread類認(rèn)識(shí)這個(gè)Runnable接口。
? ? ? ? 所以,我們?cè)趎ew Thread方法的同時(shí),就可以指定run方法所屬的對(duì)象。
? ? ? ? 編譯運(yùn)行:
? ? ? ? 搞定!
? ? ? ? 這就是創(chuàng)建線程的第二種方式:實(shí)現(xiàn)Runnable接口。
? ? ? ? 步驟:
? ? ? ? 1,定義類實(shí)現(xiàn)Runnable接口。
? ? ? ? 2,覆蓋Runnable接口中的run方法。
? ? ? ? ? ? ? ? 將線程要運(yùn)行的代碼存放在該run方法中。
? ? ? ? 3,通過(guò)Thread類建立線程對(duì)象。
? ? ? ? 4,將Runnable接口的子類對(duì)象作為實(shí)際參數(shù)傳遞給Thread類的構(gòu)造函數(shù)。
? ? ? ? ? ? ? ? 為什么要將Runnable接口的子類對(duì)象傳遞給Thread的構(gòu)造函數(shù)?
? ? ? ? ? ? ? ? 因?yàn)椋远x的run方法所屬的對(duì)象是Runnable接口的子類對(duì)象。
? ? ? ? ? ? ? ? 所以要讓線程去指定對(duì)象的run方法。就必須明確該run方法所屬的對(duì)象。
? ? ? ? 5,調(diào)用Thread類的start方法開(kāi)啟線程,并調(diào)用Runnable接口子類的run方法。
? ? ? ? 那么,這兩種創(chuàng)建線程的方式,也就是實(shí)現(xiàn)方式和繼承方式有什么區(qū)別呢?
? ? ? ? 我們先講一下Runnable接口的由來(lái):
? ? ? ? 左邊的Student類,沒(méi)有父類,它里面的run方法需要被多線程執(zhí)行,所以就繼承了Thread類。
? ? ? ? 可是,Student在演變的過(guò)程中,抽取出來(lái)了一個(gè)父類Person,這個(gè)時(shí)候Student類中的run方法需要被多線程執(zhí)行,但是因?yàn)樗呀?jīng)有父類了,而Java又不支持多繼承,所以無(wú)法再繼承Thread類。
? ? ? ? 這時(shí),Java提供了一個(gè)Runnable接口來(lái)解決這個(gè)問(wèn)題,“你可以不叫我爸爸,我也可以幫你執(zhí)行代碼,只要你符合我的規(guī)則就行”,將這個(gè)功能抽取出來(lái)封裝到了Runnable接口中。
? ? ? ? 這就是接口的由來(lái)。
? ? ? ? 因此,實(shí)現(xiàn)方式的好處:避免了單繼承的局限性,把它作為了一種功能的擴(kuò)展封裝在了接口中。在定義線程時(shí),建議使用實(shí)現(xiàn)方式。
? ? ? ? 而且這個(gè)構(gòu)造方法使用了多態(tài):
? ? ? ? 因?yàn)槿蘸髸?huì)出現(xiàn)什么樣的接口子類是無(wú)法預(yù)知的,所以它用了多態(tài)的形式,你只要符合規(guī)則,它都可以用!你只要符合PCI的規(guī)則,后期粗現(xiàn)什么樣的版本,它都可以幫你運(yùn)行!
? ? ? ? 我們發(fā)現(xiàn),Thread類本身也實(shí)現(xiàn)了Runnable接口:
? ? ? ? Runnable接口的定義,其實(shí)就是在確立線程要運(yùn)行代碼所存放的位置。
? ? ? ? 兩種創(chuàng)建線程的方式還有一個(gè)區(qū)別:
? ? ? ? 繼承Thread:線程代碼存放在Thread子類的run方法中。
? ? ? ? 實(shí)現(xiàn)Runnable:線程代碼存放在接口子類的run方法中。
? ? ? ? 它們代碼的存放位置不一樣。
? ? ? ? 當(dāng)然,如果你的類沒(méi)有父類,用第一種方式也是完全可以的。
????09-多線程(多線程的安全問(wèn)題)
? ? ? ? 現(xiàn)在只剩最后一張票了,然后4個(gè)窗口都拿到了執(zhí)行資格,在等待執(zhí)行權(quán),最后執(zhí)行權(quán)比如說(shuō)給了0號(hào),0號(hào)賣出后執(zhí)行權(quán)比如給了1號(hào),這個(gè)時(shí)候1再繼續(xù)執(zhí)行票數(shù)就為負(fù)了,這顯然不符合常理。我們的程序有bug啦!?
? ? ? ? 我們現(xiàn)在模擬一下這個(gè)過(guò)程,讓我們真實(shí)的看到這個(gè)現(xiàn)象。
? ? ? ? 怎么做呢?
? ? ? ? 那怎么讓它睡一下呢?
? ? ? ? 看一下sleep方法:
? ? ? ? 我們看一下這個(gè)方法的特點(diǎn):
? ? ? ? 它是靜態(tài)的,沒(méi)有訪問(wèn)到對(duì)象的特有數(shù)據(jù),并且它拋出了異常。
? ? ? ? 話不多說(shuō),寫(xiě)起來(lái):
? ? ? ? 因?yàn)門icket實(shí)現(xiàn)了Runnable接口,而這個(gè)接口并未拋出異常,所以Ticket也不能拋出異常,所以只能try哦。
? ? ? ? 運(yùn)行一下:
? ? ? ? 剛剛那個(gè)問(wèn)題就出現(xiàn)啦。
? ? ? ? 通過(guò)分析,發(fā)現(xiàn),打印出0,-1,-2等錯(cuò)票。
? ? ? ? 多線程的運(yùn)行出現(xiàn)了安全問(wèn)題。
? ? ? ? 安全問(wèn)題最可怕辣!
? ? ? ? 多線程中一定要小心安全問(wèn)題,一旦產(chǎn)生就非常要命。
? ? ? ? 問(wèn)題的原因:
? ? ? ? 當(dāng)多條語(yǔ)句在操作同一個(gè)線程共享數(shù)據(jù)時(shí),一個(gè)線程對(duì)多條語(yǔ)句只執(zhí)行了一部分,還沒(méi)有執(zhí)行完,另一個(gè)線程參與進(jìn)來(lái)執(zhí)行,導(dǎo)致了共享數(shù)據(jù)的錯(cuò)誤。
? ? ? ? 解決辦法:
? ? ? ? 對(duì)多條操作共享數(shù)據(jù)的語(yǔ)句,只能讓一個(gè)線程都執(zhí)行完,在執(zhí)行過(guò)程中,其他過(guò)程不可以參與執(zhí)行。
? ? ? ? Java對(duì)于多線程的安全問(wèn)題提供了專業(yè)的解決方式。
? ? ? ? 就是同步代碼塊。
? ? ? ? synchronized(對(duì)象)
? ? ? ? {
? ? ? ? ? ? ? ? 需要被同步的代碼
? ? ? ? }
? ? ? ? 寫(xiě)起來(lái)~
? ? ? ? 運(yùn)行看一下效果:
? ? ? ? OK啦!
? ??10-多線程(多線程同步代碼塊)
? ? ? ? 在上節(jié)課中,這里有個(gè)對(duì)象:
? ? ? ? 這個(gè)對(duì)象如同鎖,持有鎖的線程可以在同步中執(zhí)行。
? ? ? ? 沒(méi)有持有鎖的線程,即使獲得了cpu的執(zhí)行權(quán),也進(jìn)不去,因?yàn)闆](méi)有獲取鎖。
? ? ? ? 火車上的衛(wèi)生間就是一個(gè)經(jīng)典的的例子。
? ? ? ? 同步的前提:
? ? ? ? 1,必須要有兩個(gè)或者兩個(gè)以上的線程才需要同步。(就你一個(gè)人用這個(gè)衛(wèi)生間,就不需要鎖門啦)
? ? ? ? 2,必須是多個(gè)線程使用同一個(gè)鎖才需要同步。(多個(gè)人使用的是同一個(gè)衛(wèi)生間)
? ? ? ? 必須保證同步中只能有一個(gè)線程在運(yùn)行。
? ? ? ? 同步的好處:
? ? ? ? 解決了多線程的安全問(wèn)題。
? ? ? ? 不過(guò)它也有弊端:雖然解決了問(wèn)題,但是執(zhí)行的時(shí)候每次都要判斷這個(gè)鎖,所以較為消耗資源。(越安全越麻煩)