Java基礎(chǔ)之多線程
線程中run()和start()的區(qū)別:
對(duì)于Thread對(duì)象來說,當(dāng)你調(diào)用的是start()
,線程會(huì)被放到等待隊(duì)列,等待CPU調(diào)度,不一定馬上執(zhí)行;無需等待run()
方法執(zhí)行完畢,可以直接執(zhí)行下面的代碼;
而調(diào)用的是run()的話,就是當(dāng)做普通的方法調(diào)用,程序還是要順序執(zhí)行的;
新建線程的幾種方式:
實(shí)現(xiàn)Runnable接口;里面實(shí)現(xiàn)run()方法;
然后把這個(gè)實(shí)現(xiàn)了Runnable接口的類就新建為一個(gè)Thread t = new Thread(new (實(shí)現(xiàn)Runnable接口的類)),調(diào)用start()方法即可開始一個(gè)線程了。記住,start()只是開啟,然后就會(huì)返回,繼續(xù)執(zhí)行start()下面的語句了。
線程執(zhí)行器:
我們可以通過不同的線程執(zhí)行器來實(shí)現(xiàn)多線程的執(zhí)行,有以下幾種執(zhí)行器:
ExecutorService exec = Executors.newCachedThreadPool();
ExecutorService exec = Executors.newFixedThreadPool(5);
ExecutorService exec = Executors.newSingleThreadExecutor();
我們可以對(duì)比一下這三者的區(qū)別:第一個(gè)執(zhí)行會(huì)為每一個(gè)任務(wù)都創(chuàng)建一個(gè)線程,
而第二個(gè)則是可以一次性指定要分配多少線程,而第三個(gè)則是屬于單線程,會(huì)一個(gè)線程一個(gè)線程的依次執(zhí)行;
休眠:
會(huì)使得任務(wù)中斷一段時(shí)間,相當(dāng)于變相的阻塞了,可以給其他線程制造機(jī)會(huì)去執(zhí)行;
但是我們不能通過sleep()
來試圖控制線程的順序執(zhí)行,而是要考慮用同步控制來實(shí)現(xiàn);
讓步:
通過使用yield()
方法來給線程調(diào)度機(jī)制一個(gè)暗示:你的工作已經(jīng)完成的差不多了,可以讓別的線程使用CPU了,其功能上跟sleep()
其實(shí)是差不多的。
后臺(tái)線程:
指在程序運(yùn)行的時(shí)候在后臺(tái)提供一種通用服務(wù)的線程,并且這種線程并不屬于程序總共不可或缺的部分,當(dāng)所有非后臺(tái)線程結(jié)束時(shí),程序終止;由后臺(tái)線程創(chuàng)建的線程也是后臺(tái)線程;
在線程調(diào)用start()
之前,調(diào)用setDaemon(true);
實(shí)現(xiàn)多線程的另一種方式:
通過繼承Thread的方式來實(shí)現(xiàn):而且run()
方法是放在構(gòu)造函數(shù)里面的,也就是說,當(dāng)初始化一個(gè)線程的時(shí)候,就自動(dòng)的開啟了線程,記得run()方法里面一般都是一個(gè)while()
循環(huán);
加入一個(gè)線程:
一個(gè)線程可以在其他線程之上調(diào)用join()
方法,如果某個(gè)線程在另一個(gè)線程t上調(diào)用t.join();
此線程將被掛起,知道目標(biāo)線程t結(jié)束才恢復(fù);
join()方法,你在一個(gè)線程中join()了一個(gè)線程進(jìn)來,你就要等待這個(gè)線程結(jié)束了,才可以把自己這個(gè)線程給結(jié)束掉;join()的底層實(shí)現(xiàn)是wait()
方法;
同步:
Synchronzied;可以用在方法上,也可以用到類上面;
顯式地使用lock對(duì)象
先用Lock lock = new ReentrantLock();
建出一個(gè)鎖對(duì)象出來,然后在方法里面,先調(diào)用lock.lock();
然后try語句里面是方法體,最后記得要在finally里面加上lock.unlock();這樣就就相當(dāng)于解鎖了。
區(qū)別:
可以看到synchronized和 lock相比起來,lock似乎要加上一些try/catch語句才可以,但是,這也是好處之一,比起synchronized,可以多出來處理的過程,讓用戶出現(xiàn)錯(cuò)誤的可能性降低;
使用原子類也可以實(shí)現(xiàn)資源共享的問題,但是原子類一般很少在常規(guī)編程中用到,用于性能調(diào)優(yōu),然后AtomicInteger,AtomicLong等原子類,使用這些的時(shí)候,不需要用到synchronized和lock,但是原子類很少用到,所以我們還是用synchronized和lock。
線程的狀態(tài):
新建;就緒;阻塞;死亡;
導(dǎo)致阻塞的幾個(gè)原因:
1,通過調(diào)用sleep()
使任務(wù)進(jìn)入休眠狀態(tài),
2,通過調(diào)用wait()
使線程掛起,知道線程得到notify()
或notifyAll()
消息,
3,任務(wù)再等待某個(gè)輸入/輸出完成;
4,任務(wù)視圖在某個(gè)對(duì)象上調(diào)用其同步控制方法,但是對(duì)象鎖不可用,因?yàn)榱硪粋€(gè)任務(wù)已經(jīng)獲取了這個(gè)鎖;
中斷:
這是一個(gè)大學(xué)問呀。一般的話,我們中斷都是用interrupted(),
但是,我們現(xiàn)在說了,用Executor執(zhí)行器可以更好地執(zhí)行了,所以我們?nèi)缛绾卧趫?zhí)行器中中斷線程呢?這也很好辦,用Executor的shutdownNow(),但是,這又是一個(gè)問題了,這只是用來中斷所有的線程的,但是我們是想要中斷某一個(gè)線層那該怎么辦呢?這就用到了返回式了,通過submit()
來啟動(dòng)任務(wù)的時(shí)候,我們就能夠得到返回的類型Future<?>通過這個(gè)去調(diào)用calcel()來中斷某個(gè)線程。具體等一下碼,現(xiàn)在還要討論的還有一個(gè)問題,中斷的線程是否有一些是無法中斷的,判定如下:如果是在sleep()中的線程,那么顯然是可以中斷的,但是對(duì)于正在讀取I/O的線程和正在試圖獲取鎖的線程,我們是無法中斷的,而中斷線程就相當(dāng)于拋出了一個(gè)異常,方便我們關(guān)閉掉資源。
線程之間的協(xié)作:
當(dāng)線程同時(shí)運(yùn)行多個(gè)任務(wù)時(shí),我們可以用鎖來同步兩個(gè)任務(wù)的行為,同時(shí)也可以用wait()
和notifyAll()
來實(shí)現(xiàn)對(duì)線程的控制;
wait()就是一種掛起的狀態(tài),當(dāng)你掛起了之后,鎖將被釋放,把空出來的線程,給別人執(zhí)行,而等到被調(diào)用notify()喚醒之后,又會(huì)重新獲得之前wait()鎖,如果這個(gè)時(shí)候鎖正在被使用的話,就要陷入等待了。
wait()跟sleep()之間的區(qū)別:
1,在synchronized中,wait()期間對(duì)象鎖是釋放的;而sleep()鎖是不會(huì)釋放的;
2,可以通過notify(),notifyAll(),或者令時(shí)間到期,從wait()中恢復(fù)執(zhí)行;
喚醒的區(qū)別:
notify()方法保證的喚醒是指喚醒的是恰當(dāng)?shù)娜蝿?wù),另外,為了使用notify(),你必須等待相同的條件,而對(duì)于notifyAll()來說,是否所有的線程都會(huì)被喚醒呢?只有當(dāng)notifyAll()因某個(gè)特定鎖被調(diào)用時(shí),只有等待這個(gè)鎖的任務(wù)才能被喚醒;
除了wait()和notify(),我們還可以顯式地使用lock和Condition對(duì)象;
使用互斥并允許任務(wù)掛起的基本類是Condition;可以通過在Condition上調(diào)用await來掛起一個(gè)任務(wù),通過signal()
來通知這個(gè)任務(wù),喚醒這個(gè)任務(wù),或者調(diào)用signalAll()來喚醒所有在這個(gè)Condition上被其自身掛起的任務(wù);與使用notify()相比,signlAll()是更安全的方式;
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); lock.unlock(); condition.await();
但是我們要知道顯式的lock對(duì)象,相比起wait(),notify()來說,更加復(fù)雜,所以還是建議用回原來的那個(gè)wait(),Lock和Condition只有在更加困難的多線程問題才是必需的;
死鎖:
當(dāng)某一個(gè)任務(wù)在等待另一個(gè)任務(wù)的鎖釋放,而下一個(gè)任務(wù)又在等待上一個(gè)鎖的釋放,在這樣呈鏈?zhǔn)降难h(huán)里面,直到這個(gè)鏈條上的任務(wù)又在等待第一個(gè)任務(wù)釋放鎖,得到了一個(gè)任務(wù)之間的相互等待的連續(xù)循環(huán);稱為死鎖;
哲學(xué)家就餐問題;經(jīng)典的死鎖問題;
發(fā)生死鎖的四個(gè)滿足條件:
1,互斥條件,任務(wù)使用的資源中國至少有一個(gè)是不能共享的。
2,至少有一個(gè)任務(wù)它必須持有一個(gè)資源把那個(gè)正在等待獲取另一個(gè)被別的任務(wù)持有的資源;
3,資源不能被任務(wù)搶占;
4,必須要有等待循環(huán);
防止死鎖的最容易的方式是破壞第四個(gè)條件。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
Springboot actuator應(yīng)用后臺(tái)監(jiān)控實(shí)現(xiàn)
這篇文章主要介紹了Springboot actuator應(yīng)用后臺(tái)監(jiān)控實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04SpringBoot實(shí)現(xiàn)多端口監(jiān)聽的代碼示例
當(dāng)你需要在同一個(gè)Spring Boot應(yīng)用中,通過不同的端口來提供不同的服務(wù)或功能時(shí),就需要實(shí)現(xiàn)多端口監(jiān)聽,所以本文給大家介紹了SpringBoot實(shí)現(xiàn)多端口監(jiān)聽的方法示例,并有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-09-09java后臺(tái)調(diào)用接口及處理跨域問題的解決
這篇文章主要介紹了java后臺(tái)調(diào)用接口,處理跨域的問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Mybatis generator mapper文件覆蓋原文件的示例代碼
這篇文章主要介紹了Mybatis generator mapper文件覆蓋原文件,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11解決springboot接入springfox-swagger2遇到的一些問題
這篇文章主要介紹了解決springboot接入springfox-swagger2遇到的一些問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07在RedisTemplate中使用scan代替keys指令操作
這篇文章主要介紹了在RedisTemplate中使用scan代替keys指令操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11SpringBoot整合Caffeine實(shí)現(xiàn)本地緩存的實(shí)踐分享
緩存是提升系統(tǒng)性能的一個(gè)不可或缺的工具,通過緩存可以避免大部分重復(fù)的請求到數(shù)據(jù)庫層,減少IO鏈接次數(shù),提升整體的響應(yīng)速率,本地緩存中比較常見的比如 Caffeine 緩存,這篇文章將結(jié)合具體的 Springboot 項(xiàng)目搭配 Caffeine 實(shí)現(xiàn)本地緩存的各種使用方式2024-07-07