Java多線程(Thread,Runnable,Callable)附帶相關(guān)面試題

1.通過繼承Thread類實現(xiàn)多線程
子類通過繼承Thread父類并覆寫其中的run方法。run方法實現(xiàn)線程需要完成的任務(wù),最后在主類中實例化子類(即創(chuàng)建線程)并調(diào)用start()方法,讓創(chuàng)建的線程工作。
?案例1 售票員在票出售光前實現(xiàn)一直出售:

?調(diào)用代碼后發(fā)現(xiàn)代碼出現(xiàn)幾個問題:
1.售票員售票速度太快了
2.多個售票員之間100張票出現(xiàn)售票混亂的情況,比如只有一張8號的票卻同時賣出去了
為了解決這些問題我們就需要引入多線程常用的操作方法
*面試題:線程的run()和start()有什么區(qū)別?
每個線程都是通過某個特定Thread對象所對應(yīng)的方法run()來完成其操作的,run()方法稱為線程體。通過調(diào)用Thread類的start()方法來啟動一個線程。
start() 方法用于啟動線程,run() 方法用于執(zhí)行線程的運行時代碼。run() 可以重復(fù)調(diào)用,而 start() 只能調(diào)用一次。
start()方法來啟動一個線程,真正實現(xiàn)了多線程運行。調(diào)用start()方法無需等待run方法體代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行其他的代碼; 此時線程是處于就緒狀態(tài),并沒有運行。 然后通過此Thread類調(diào)用方法run()來完成其運行狀態(tài), run()方法運行結(jié)束, 此線程終止。然后CPU再調(diào)度其它線程。
run()方法是在本線程里的,只是線程里的一個函數(shù),而不是多線程的。 如果直接調(diào)用run(),其實就相當于是調(diào)用了一個普通函數(shù)而已,直接待用run()方法必須等待run()方法執(zhí)行完畢才能執(zhí)行下面的代碼,所以執(zhí)行路徑還是只有一條,根本就沒有線程的特征,所以在多線程執(zhí)行時要使用start()方法而不是run()方法。

2.多線程常用操作方法
Thread.sleep():讓當前線程休眠指定的時間,暫停執(zhí)行,不釋放鎖資源。
Thread.interrupt():中斷當前線程,給線程發(fā)送中斷信號,該線程需要處理中斷信號來決定如何終止執(zhí)行。
Thread.currentThread().getName():獲取當前線程的名稱。
Thread.currentThread().setName(String name):設(shè)置當前線程的名稱。
Thread.yield():暫停當前正在執(zhí)行的線程,讓其他具有相同優(yōu)先級的線程有機會執(zhí)行。
Thread.join():等待指定線程終止執(zhí)行,當前線程進入阻塞狀態(tài),直到指定線程執(zhí)行結(jié)束。
Thread.wait():在等待其他線程通知之前,使當前線程進入等待狀態(tài),并釋放對象的監(jiān)視器鎖資源。
?首先是解決售票員售票速度太快了問題,查看方法可以發(fā)現(xiàn)Thread.sleep()就能解決這個問題
注意Thread.sleep()會拋出一個異常interruptException表示線程睡眠被打斷了。所以需要用try catch去處理這個可能發(fā)送的異常
?修改后的案例代碼2:

? 接下來就是解決同步問題了,由上圖可以看到雖然解決了賣得快得問題,但是并發(fā)問題并未解決,并發(fā)就是指多個線程同時調(diào)用資源,同時修改資源得情況。需要用到同步synchronized實現(xiàn)同步,這里只展示實現(xiàn)代碼
案例代碼:(售票員獨立售賣電影票并且在隨機時間內(nèi)售出)

其他功能 案例:
1.線程中斷

2.強制執(zhí)行
就是指如果一個線程使用了Thread 對象.join();那么他就是老大,所有資源都獨享,其他線程只能等待老大享受完成資源。

3.線程禮讓
Thread.yield();線程會讓出一些
資源出來,在資源不緊缺時候再調(diào)用該程序。但還是可能會出現(xiàn)交替運行的情況

*面試題Thread 類中的 yield 方法有什么作用?
yield()應(yīng)該做的是讓當前運行線程回到可運行狀態(tài),以允許具有相同優(yōu)先級的其他線程獲得運行機會。因此,使用yield()的目的是讓相同優(yōu)先級的線程之間能適當?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程調(diào)度程序再次選中。
結(jié)論:yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)。在大多數(shù)情況下,yield()將導(dǎo)致線程從運行狀態(tài)轉(zhuǎn)到可運行狀態(tài),但有可能沒有效果。

3.通過Runnable接口實現(xiàn)多線程
Runnable的接口代碼:
設(shè)置類實現(xiàn)Runnable接口并覆寫run()方法,最后在主類先創(chuàng)建類的實例對象,再創(chuàng)建線程對象通過線程的構(gòu)造函數(shù)Thread(Task,"threadName"->名字可選,系統(tǒng)會自動構(gòu)造名字)實現(xiàn)線程運行Task類。
案例1:(通過實現(xiàn)Runnable實現(xiàn)售票員售票)

案例代碼2:

?多線程中Thread與Runnable的區(qū)別:
繼承Thread類:這種方式是創(chuàng)建一個新的類,繼承自Thread類,并重寫它的run()方法來定義線程執(zhí)行的邏輯。通過創(chuàng)建Thread類的實例對象,可以直接調(diào)用其start()方法來啟動線程。這種方式的優(yōu)點是代碼簡單,方便使用,但缺點是由于Java不支持多重繼承,因此如果已經(jīng)繼承了其他類,則無法再使用這種方式創(chuàng)建線程。
實現(xiàn)Runnable接口:這種方式是創(chuàng)建一個實現(xiàn)Runnable接口的類,在該類中實現(xiàn)run()方法來定義線程執(zhí)行的邏輯。然后,創(chuàng)建Thread類的實例對象時,將實現(xiàn)了Runnable接口的類的實例對象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)。最后,調(diào)用Thread實例對象的start()方法來啟動線程。這種方式的優(yōu)點是避免了單繼承的限制,提高了代碼的靈活性和可復(fù)用性。

4.通過Lambda與Thread結(jié)合實現(xiàn)快速創(chuàng)建多線程
當然Thread構(gòu)造函數(shù)的Task可以與Lambda表達式結(jié)合實現(xiàn)不需要構(gòu)造子類就能夠?qū)崿F(xiàn)多線程
回顧以往的Lambda的知識,核心就兩種實現(xiàn)方法
1.()->{方法體}
2.()->語句
案例3

到現(xiàn)在以上所有Thread,還是Runnable可以發(fā)現(xiàn)都沒有一個這些類或者接口中的run()方法是沒有任何返回值的,為了解決這個問題java提供了Callable接口

5.通過實現(xiàn)Callable接口得到線程返回值
由于用Thread會有單繼承限制,而用Runnable會有run方法無法獲取返回值的缺點,所以為了獲取返回值則使用Callable泛型,Callable的接口如下:
可以看到Callable使用了泛型類,目的在于可以返回不同類型的值,V可以設(shè)置為String,int,double等等類型。并且實現(xiàn)這個類需要用FutureTask進行接收子類
FutureTask <返回類型> Task = new FutureTask<>(實例化創(chuàng)建的任務(wù)類的對象)
new Thread(Task,"Threadname").start();
task.get();獲取返回值
多線程中Runnable與Callable的區(qū)別
返回值類型: Runnable接口的run()方法沒有返回值,它的執(zhí)行結(jié)果無法獲取。而Callable接口的call()方法可以返回一個結(jié)果,并且可以通過Future對象獲取該結(jié)果。
異常處理: Runnable接口的run()方法無法拋出受檢查異常,只能在方法內(nèi)部進行異常處理。而Callable接口的call()方法可以拋出受檢查異常,需要在方法內(nèi)部進行異常處理或者通過Future對象獲取異常信息。
支持泛型: Callable接口是一個泛型接口,可以指定call()方法的返回類型。而Runnable接口不支持泛型,無法指定返回類型。
使用方式: Runnable接口通常與Thread類一起使用,通過創(chuàng)建Thread類的實例并傳入一個Runnable對象來創(chuàng)建線程。Callable接口通常與ExecutorService線程池一起使用,通過提交Callable任務(wù)給線程池來創(chuàng)建線程,并通過Future對象獲取任務(wù)的執(zhí)行結(jié)果。