Java 線程對(duì)比(Thread,Runnable,Callable)實(shí)例詳解
Java 線程對(duì)比Thread,Runnable,Callable
java 使用 Thread 類代表線程,所有現(xiàn)場(chǎng)對(duì)象都必須是 Thread 類或者其子類的實(shí)例。每個(gè)線程的作用是完成一定的任務(wù),實(shí)際上就是執(zhí)行一段程序流。java 使用線程執(zhí)行體來代表這段程序流。
1.繼承Thread 類創(chuàng)建線程
啟動(dòng)多線程的步驟如下:
(1)定義Thread 類的子類,并重寫該類的run() 方法,該run() 方法的方法體就代表類線程需要完成的任務(wù)。因此把run() 方法稱為線程執(zhí)行體。
(2)創(chuàng)建 Thread 子類的實(shí)例,即創(chuàng)建線程對(duì)象。
(3)調(diào)用線程的star()方法來啟動(dòng)該線程。
相關(guān)代碼如下:
/** * 繼承 thread 的內(nèi)部類,以買票例子 */ public class FirstThread extends Thread{ private int i; private int ticket = 10; @Override public void run() { for (;i<20;i++) { //當(dāng)繼承thread 時(shí),直接使用this 可以獲取當(dāng)前的線程,getName() 獲取當(dāng)前線程的名字 // Log.d(TAG,getName()+" "+i); if(this.ticket>0){ Log.e(TAG, getName() + ", 賣票:ticket=" + ticket--); } } } } private void starTicketThread(){ Log.d(TAG,"starTicketThread, "+Thread.currentThread().getName()); FirstThread thread1 = new FirstThread(); FirstThread thread2 = new FirstThread(); FirstThread thread3 = new FirstThread(); thread1.start(); thread2.start(); thread3.start(); //開啟3個(gè)線程進(jìn)行買票,每個(gè)線程都賣了10張,總共就30張票 }
運(yùn)行結(jié)果:
可以看到 3 個(gè)線程輸入的 票數(shù)變量不連續(xù),注意:ticket 是 FirstThread 的實(shí)例屬性,而不是局部變量,但是因?yàn)槌绦蛎看蝿?chuàng)建線程對(duì)象都需要?jiǎng)?chuàng)建一個(gè)FirstThread 的對(duì)象,所有多個(gè)線程不共享該實(shí)例的屬性。
2.實(shí)現(xiàn) Runnable 接口創(chuàng)建線程
注意:public class Thread implements Runnable
(1)定義 Runnable 接口的實(shí)現(xiàn)類,并重寫該接口的run()方法,該run() 方法的方法體同樣是該線程的線程執(zhí)行體。
(2)創(chuàng)建 Runnable 實(shí)例類的實(shí)例,此實(shí)例作為 Thread 的 target 來創(chuàng)建Thread 對(duì)象,該Thread 對(duì)象才是真正的對(duì)象。
相關(guān)代碼如下:
/** * 實(shí)現(xiàn) runnable 接口,創(chuàng)建線程類 */ public class SecondThread implements Runnable{ private int i; private int ticket = 100; @Override public void run() { for (;i<20;i++) { //如果線程類實(shí)現(xiàn) runnable 接口 //獲取當(dāng)前的線程,只能用 Thread.currentThread() 獲取當(dāng)前的線程名 Log.d(TAG,Thread.currentThread().getName()+" "+i); if(this.ticket>0){ Log.e(TAG, Thread.currentThread().getName() + ", 賣票:ticket=" + ticket--); } } } } private void starTicketThread2(){ Log.d(TAG,"starTicketThread2, "+Thread.currentThread().getName()); SecondThread secondThread = new SecondThread(); //通過new Thread(target,name)創(chuàng)建新的線程 new Thread(secondThread,"買票人1").start(); new Thread(secondThread,"買票人2").start(); new Thread(secondThread,"買票人3").start(); //雖然是開啟了3個(gè)線程,但是一共只買了100張票 }
運(yùn)行結(jié)果:
可以看到 3 個(gè)線程輸入的 票數(shù)變量是連續(xù)的,采用 Runnable 接口的方式創(chuàng)建多個(gè)線程可以共享線程類的實(shí)例的屬性。這是因?yàn)樵谶@種方式下,程序所創(chuàng)建的Runnable 對(duì)象只是線程的 target ,而多個(gè)線程可以共享同一個(gè) target,所以多個(gè)線程可以共享同一個(gè)線程類(實(shí)際上應(yīng)該是該線程的target 類)的實(shí)例屬性。
3.使用 Callable 和Future 創(chuàng)建線程
從 java 5 開始,Java 提供了 Callable 接口,該接口是runnable 的增強(qiáng)版,Callable 提供類一個(gè) call() 方法可以作為線程執(zhí)行體,但是call() 方法的功能更強(qiáng)大。
(1) call() 方法可以有返回值
(2) call() 方法可以聲明拋出異常
因此我們完全可以提供一個(gè)callable 對(duì)象作為Thread的 target ,而該線程的執(zhí)行體就是該callable 對(duì)象的call() 方法。同時(shí) java 5 提供了 Future 接口 來代表Callable 接口里 call() 方法的返回值,并且提供了一個(gè) futureTask 的實(shí)現(xiàn)類,該實(shí)現(xiàn)類實(shí)現(xiàn)類 future 接口,并實(shí)現(xiàn)了runnable 接口—可以作為Thread 類的target.
啟動(dòng)步驟如下:
(1)創(chuàng)建callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call() 方法,該call() 方法將作為線程的執(zhí)行體,且該call() 方法是有返回值的。
(2)創(chuàng)建 callable實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來包裝Callable對(duì)象,該FutureTask 對(duì)象封裝 call() 方法的返回值。
(3)使用FutureTask 對(duì)象作為Thread對(duì)象的target創(chuàng)建并啟動(dòng)新線程。
(4)調(diào)用FutureTask對(duì)象的get()方法來獲取子線程執(zhí)行結(jié)束后的返回值。
相關(guān)代碼如下:
/** * 使用callable 來實(shí)現(xiàn)線程類 */ public class ThirdThread implements Callable<Integer>{ private int ticket = 20; @Override public Integer call(){ for ( int i = 0;i<10;i++) { //獲取當(dāng)前的線程,只能用 Thread.currentThread() 獲取當(dāng)前的線程名 // Log.d(TAG,Thread.currentThread().getName()+" "+i); if(this.ticket>0){ Log.e(TAG, Thread.currentThread().getName() + ", 賣票:ticket=" + ticket--); } } return ticket; } } private void starCallableThread(){ ThirdThread thirdThread = new ThirdThread(); FutureTask<Integer> task = new FutureTask<Integer>(thirdThread); new Thread(task,"有返回值的線程").start(); try { Integer integer = task.get(); Log.d(TAG,"starCallableThread, 子線程的返回值="+integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
運(yùn)行結(jié)果:
注意:Callable的call() 方法允許聲明拋出異常,并且允許帶有返回值。
程序最后調(diào)用FutureTask 對(duì)象的get()方法來返回Call()方法的返回值,導(dǎo)致主線程被阻塞,直到call()方法結(jié)束并返回為止。
4.三種方式的對(duì)比
采用繼承Thread 類的方式創(chuàng)建多線程
劣勢(shì): 已經(jīng)繼承Thread類不能再繼承其他父類。
優(yōu)勢(shì): 編寫簡(jiǎn)單
采用繼承Runnable,Callable 接口的方式創(chuàng)建多線程
劣勢(shì): 編程稍微有點(diǎn)復(fù)雜,如果需要訪問當(dāng)前線程必須使用Thread.currentThread()
優(yōu)勢(shì):
(1)還可以繼承其他類
(2)多個(gè)線程可以共享一個(gè)target 對(duì)象,所以非常適合多個(gè)相同的線程來處理同一份資源的情況,從而將cpu,代碼和數(shù)據(jù)分開,形成清晰的模型,較好的體現(xiàn)類面向?qū)ο蟮乃枷搿?/p>
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Java打包之后讀取Resources下的文件失效原因及解決方法
這篇文章主要給大家介紹了Java打包之后讀取Resources下的文件失效的問題分析和解決方法,文中通過代碼示例和圖文結(jié)合給大家講解非常詳細(xì),需要的朋友可以參考下2023-12-12解決SpringBoot加載application.properties配置文件的坑
這篇文章主要介紹了SpringBoot加載application.properties配置文件的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Spring集成Web環(huán)境與SpringMVC組件的擴(kuò)展使用詳解
這篇文章主要介紹了Spring集成Web環(huán)境與SpringMVC組件,它是一個(gè)MVC架構(gòu),用來簡(jiǎn)化基于MVC架構(gòu)的Web應(yīng)用開發(fā)。SpringMVC最重要的就是五大組件2022-08-08淺談springboot多模塊(modules)開發(fā)
這篇文章主要介紹了淺談springboot多模塊(modules)開發(fā),詳細(xì)的介紹了springboot多模塊的實(shí)現(xiàn),有興趣的可以了解一下2017-09-09java實(shí)現(xiàn)在線預(yù)覽--poi實(shí)現(xiàn)word、excel、ppt轉(zhuǎn)html的方法
這篇文章主要介紹了java實(shí)現(xiàn)在線預(yù)覽--poi實(shí)現(xiàn)word、excel、ppt轉(zhuǎn)html的方法,本文需要引入poi的jar包給大家介紹的非常詳細(xì),需要的朋友可以參考下2019-09-09