Java多線程總結(jié)

在Java中引入多線程的目的顯而易見,當(dāng)程序中有多部分代碼需要同時(shí)執(zhí)行,這時(shí)便需要引入多線程,將需要同時(shí)執(zhí)行的代碼作為線程任務(wù)(并發(fā)任務(wù)),來達(dá)到目的。

同時(shí)在這里,還有必要搞清一個(gè)問題,什么是同時(shí)執(zhí)行?從字面意思來理解,就是同時(shí)執(zhí)行!而事實(shí)上,是CPU瞬間在各個(gè)線程之間做著快速切換,這種切換在引入時(shí)間片技術(shù)的OS系統(tǒng)中,是按照時(shí)間片的技術(shù)來完成的。

在這行main函數(shù)中內(nèi)容的同時(shí),垃圾回收器的線程,同時(shí)執(zhí)行!

線程創(chuàng)建的方式有兩種

方式一:繼承Thread類,覆蓋run()方法

步驟:

1.定義類覆蓋Thread類;

2.覆蓋Thread類中的run方法;

3.創(chuàng)建Thread類的子類對(duì)象創(chuàng)建線程對(duì)象;

4.調(diào)用線程對(duì)象的start()方法,開啟線程。


方式二:實(shí)現(xiàn)Runnable接口,重寫run()方法

1.定義一個(gè)類實(shí)現(xiàn)Runnable接口;

2.覆蓋Runnable接口中的run()方法,將線程要運(yùn)行的代碼存儲(chǔ)到run()方法中;

3.創(chuàng)建該接口的子類對(duì)象;

4.通過Thread類進(jìn)行線程的創(chuàng)建,并將Runnable接口的子類作為Thread構(gòu)造參數(shù)的實(shí)參進(jìn)行傳遞;

5.調(diào)用Thread類的start()開啟線程。

兩種創(chuàng)建方式的區(qū)別:

方式一:繼承自Thread類,這就會(huì)收到Java單繼承這種局限性的影響,當(dāng)我們?nèi)绻霐U(kuò)展這個(gè)線程子類的功能時(shí)就會(huì)收到很大的

限制

方式二:實(shí)現(xiàn)Runnable接口,則可以避免方式一的局限性,極大的降低了任務(wù)對(duì)象和Thread對(duì)象的耦合,更加符合Java面向?qū)ο?/p>

編程的思想

線程安全問題

說到線程安全,我會(huì)想到售票窗口售票的情況,四個(gè)售票員同時(shí)出售這100張票,用Java程序來表示的話,就是四個(gè)線程共享一個(gè)數(shù)據(jù),當(dāng)其中一個(gè)線程在處理多條操作共享數(shù)據(jù)的過程中,其他線程參與了運(yùn)算,這時(shí)就會(huì)發(fā)生線程安全問題。

線程安全的解決辦法?


只要保證一個(gè)線程在執(zhí)行多條操作共享數(shù)據(jù)的語句時(shí),其他線程不能參與運(yùn)算即可,當(dāng)該線程都執(zhí)行完后,其他線程才可執(zhí)行這些語句。

鎖--同步代碼塊,同步函數(shù),靜態(tài)同步函數(shù)使用的鎖:


1.同步代碼塊使用的鎖是:任意的對(duì)象;

2.同步函數(shù)使用的鎖是:this,this代表當(dāng)前對(duì)象的引用

3.靜態(tài)--當(dāng)一個(gè)類被加載進(jìn)內(nèi)存以后,在我們還沒有創(chuàng)建對(duì)象的時(shí)候,已經(jīng)有了xxx.class

靜態(tài)隨著類的加載而加載,這時(shí)內(nèi)存中存儲(chǔ)的對(duì)象至少有一個(gè)就是該類字節(jié)碼文件對(duì)象

這個(gè)對(duì)象的表示方式:類名.class

靜態(tài)同步函數(shù)的應(yīng)用場(chǎng)景:單例模式


單例設(shè)計(jì)模式:保證一個(gè)類在內(nèi)存中只能有一個(gè)對(duì)象

怎樣才能保證對(duì)象是唯一的呢?

1.其他程序隨時(shí)用new創(chuàng)建該類對(duì)象,無法控制個(gè)數(shù);

2.不讓其他程序創(chuàng)建,該類在本類中自己創(chuàng)建一個(gè)對(duì)象;

3.該類將創(chuàng)建的對(duì)象對(duì)位提供,讓其他程序獲取并使用。

步驟:

1.怎么實(shí)現(xiàn)不讓其他程序創(chuàng)建該類對(duì)象呢?將該類中的構(gòu)造函數(shù)私有化

2.在本類中創(chuàng)建一個(gè)本類對(duì)象,并私有化

3.定義一個(gè)方法,返回值類型是本類類型,讓其他程序通過該方法就可以獲取本類對(duì)象

懶漢式:

class Single{

private static Single s=null;

private Single(){}

public static Single getInstance(){

if(s==null){

s=new Single();

return s;

}

}

}

在并發(fā)訪問單例的時(shí)候,懶漢式單例會(huì)被破壞,有可能不能保證對(duì)象的唯一性

例如:1線程進(jìn)來后經(jīng)過判斷發(fā)現(xiàn)為null,正在準(zhǔn)備創(chuàng)建對(duì)象時(shí),CPU切換到了2線程;

2線程進(jìn)來后,經(jīng)過判斷,發(fā)現(xiàn)也為null,正在準(zhǔn)備創(chuàng)建對(duì)象時(shí);

CPU又切換到了1線程,new Single();

此時(shí)CPU又切換到了2線程,也new Single();

這就不能保證了對(duì)象的唯一性。

解決方案?同步

class Single{

private static Single s=null;

private Single(){}

public static synchronized Single getInstance(){

if(s==null){

s=new Single();

}

return s;

}

}

但是,這樣又帶來一個(gè)新的問題:當(dāng)程序中線程增多后,對(duì)鎖的判斷次數(shù)就會(huì)增多,從而影響程序的性能

那么,如何做到既保證了多線程的安全性,又可以提高程序的性能呢?那就要減少后續(xù)對(duì)鎖的判斷次數(shù)。

class Single{

private static Single s=null;

private Single(){}

private static Single getInstance(){

if(s==null){

synchronized(Single.class){

if(s=null){

s=new Single();

}

}

}

returns;

}

}

死鎖?


指多個(gè)進(jìn)程or線程在運(yùn)行過程中因爭奪資源而造成的一種僵局,當(dāng)線程處于這種僵局時(shí),若無外力作用,他們

無法繼續(xù)往前執(zhí)行。

最常見的死鎖的情況:同步的嵌套

我們應(yīng)當(dāng),盡量避免同步的嵌套情況

Thread類的方法:


run()線程中真正起作用的語句放在run()的方法體內(nèi),該方法在Thread的子類中覆蓋或在Runnable對(duì)象覆蓋

start()程序通過調(diào)用線程的start()方法執(zhí)行線程,而start()則調(diào)用run()方法

sleep(long)它的一個(gè)參數(shù)指出當(dāng)前執(zhí)行的線程應(yīng)休眠多長時(shí)間

interrupt()用于中斷一個(gè)線程

線程的生命周期



出生:新創(chuàng)建的線程處于出生born狀態(tài),在調(diào)用線程的start方法之前,該線程一直處于出生狀態(tài);

就緒:當(dāng)調(diào)用start方法后,線程便進(jìn)入了就緒狀態(tài)ready

運(yùn)行:當(dāng)系統(tǒng)給線程分配處理器資源時(shí),處于就緒狀態(tài)的最高優(yōu)先級(jí)線程便進(jìn)入了運(yùn)行狀態(tài)

死亡:當(dāng)線程的run方法結(jié)束或拋出一個(gè)未捕獲的異常,那么線程便進(jìn)入死亡狀態(tài)

阻塞:如果處于運(yùn)行狀態(tài)的線程發(fā)出IO請(qǐng)求,它便進(jìn)入了阻塞狀態(tài),當(dāng)其等待的IO操作結(jié)束后,阻塞的線程便進(jìn)入了就緒狀態(tài)

等待:當(dāng)處于運(yùn)行狀態(tài)的線程調(diào)用wait方式,線程便進(jìn)入等待狀態(tài)。它將按次序排在等待隊(duì)列中,隊(duì)列中的線程均是由于某個(gè)對(duì)象掉哦那個(gè)了wait方法才進(jìn)入等待狀態(tài)的當(dāng)與某對(duì)象相關(guān)的另一個(gè)線程調(diào)用了notify方法是,那么等待該對(duì)象所有線程都回到就緒狀態(tài)。

休眠:當(dāng)程序調(diào)用一個(gè)正在運(yùn)行的線程的sleep方法時(shí),該線程便會(huì)進(jìn)入休眠狀態(tài)

多線程之間的通信


多個(gè)線程在處理同一個(gè)資源,但是處理的動(dòng)作(線程的任務(wù))不同,通過一定的手段使各個(gè)線程能有效的利用資源,而這種手段就是等待喚醒機(jī)制。

等待喚醒機(jī)制所涉及到的方法:

Wait():等待,將正在執(zhí)行的線程釋放其執(zhí)行資格和執(zhí)行權(quán),并存儲(chǔ)到線程池中

Notify():喚醒,喚醒線程池中被wait()的線程,一次只能喚醒其中的任意一個(gè)

NotifyAll():喚醒全部,可以將線程池中的所有wait()線程都喚醒

其實(shí),所謂喚醒的意思就是讓線程池中的線程具備執(zhí)行資格,必須注意的是,這些方法都是在同步中有效的,同時(shí)這些方法在使用時(shí)必須標(biāo)明所屬鎖,這樣才可以明確出這些方法操作的到底是哪個(gè)鎖上的線程

案例:生產(chǎn)者和消費(fèi)者的問題

生產(chǎn)者生產(chǎn)饅頭,消費(fèi)者消費(fèi)饅頭….

當(dāng)生產(chǎn)者發(fā)現(xiàn)沒有饅頭時(shí),就會(huì)開始生產(chǎn),生產(chǎn)完成后,叫消費(fèi)者消費(fèi),如果發(fā)現(xiàn)有饅頭,就會(huì)wait();

當(dāng)消費(fèi)者發(fā)現(xiàn)沒有饅頭時(shí),就會(huì)wait(),當(dāng)發(fā)現(xiàn)有饅頭時(shí),就消費(fèi),然后叫生產(chǎn)者繼續(xù)生產(chǎn)。

JDK1.5后出現(xiàn)的新的接口和類:Lock:比同步函數(shù)和同步代碼塊要好一些,同步函數(shù)還是同步代碼塊所做的都是隱式的操作。并且,同步函數(shù)或者同步代碼塊使用的鎖和監(jiān)視器是同一個(gè)。

Lock接口:是將鎖進(jìn)行單獨(dú)對(duì)象的封裝,而且提供了對(duì)鎖對(duì)象很多功能,比如:lock()獲取鎖,unlock()釋放鎖。Lock對(duì)鎖的操作都是顯示操作。所以它的出線要比同步函數(shù)或者同步代碼塊明確的多,更符合面向?qū)ο蟮乃枷?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容