Java中Runnable和Callable分別什么時(shí)候使用
提到 Java 就不得不說多線程了,就算你不想說,面試官也得讓你說呀,對(duì)不對(duì)。那說到多線程,就不得提線程了(這不廢話嗎)。那說到線程,就不得不說Runnable
和Callable
這兩個(gè)家伙了。
說熟悉也是真熟悉,在剛學(xué)習(xí)多線程的時(shí)候,第一個(gè)例子大概就是下面這樣子的。
new Thread(new Runnable() { @Override public void run() { System.out.println("執(zhí)行線程" + Thread.currentThread().getName()); } }).start();
看到了 Runnable
的身影,有時(shí)候還會(huì)看到Callable
的。
但是說很熟悉吧,印象也不是很大,好像就用了一下這兩位的名號(hào),然后剩下的部分就跟他倆沒啥關(guān)系了。
今天,我們就來看看這兩位到底是什么,有什么區(qū)別,什時(shí)候應(yīng)該用 Runnable
,什么時(shí)候又應(yīng)該用 Callable
。
Runnable
自從Java誕生,Runnable
就存在了,元老中的元老了,在 1.5之前,如果你想使用線程,那必須要實(shí)現(xiàn)自 Runnable
。因?yàn)榈搅?JDK1.5,JDK 才加入了Callable
。
@FunctionalInterface public interface Runnable { public abstract void run(); }
是不是接口非常簡(jiǎn)單,就一個(gè)抽象方法。
其實(shí)還可以再簡(jiǎn)化一下, @FunctionalInterface
標(biāo)明這個(gè)接口是一個(gè)函數(shù)式接口。函數(shù)式接口是在 JDK8才加入的,為的就是實(shí)現(xiàn)函數(shù)式編程,就那種Lambada表達(dá)式。
所以在 JDK1.7中,是沒有@FunctionalInterface
修飾的,簡(jiǎn)簡(jiǎn)單單。我們找到 JDK1.7的 Ruunable
實(shí)現(xiàn),是下面這樣子
public interface Runnable { public abstract void run(); }
想了解更多函數(shù)式編程的話,可以看這篇文章:Lambda、函數(shù)式接口、Stream 一次性全給你
如果一個(gè)線程類要實(shí)現(xiàn) Runnable
接口,則這個(gè)類必須定義一個(gè)名為 run
的無參數(shù)方法。
實(shí)現(xiàn)了 Runnable
接口的類可以通過實(shí)例化一個(gè) Thread
實(shí)例,并將自身作為目標(biāo)傳遞來運(yùn)行。
舉個(gè)例子
首先定義一個(gè)RunnableThread
類,并實(shí)現(xiàn)自(implements)Runnable
接口,然后重寫 run
方法。
public class RunnableThread implements Runnable{ @Override public void run() { System.out.println("當(dāng)前線程名稱"+ Thread.currentThread().getName()); } }
使用RunnableThread
作為線程類(Thread)實(shí)例化的參數(shù),然后調(diào)用run
方法。
RunnableThread runnableThread = new RunnableThread(); Thread thread = new Thread(runnableThread); thread.start();
注意,是調(diào)用新 new 出來的 Thread 實(shí)例的start()
方法,不要調(diào)用run
方法,雖然我們是重寫Runnable
的 run
方法的。調(diào)用 run
方法并沒有創(chuàng)建線程的效果,而是直接在當(dāng)前線程執(zhí)行,就和執(zhí)行一個(gè)普通類的普通方法一模一樣。
為什么要調(diào)用 start()
方法呢,我們看看 Thread
的 start()
方法實(shí)現(xiàn)中,其實(shí)是調(diào)用了一個(gè)名稱為 start0()
的 native 方法,native 方法就不是用 Java 實(shí)現(xiàn)的了,而是在 JVM 層面的實(shí)現(xiàn)。
這個(gè)start0
方法的主要邏輯就是啟動(dòng)一個(gè)操作系統(tǒng)線程,并和 JVM 線程綁定,開辟一些空間來存儲(chǔ)線程狀態(tài)和上下文的數(shù)據(jù),然后執(zhí)行綁定的 JVM 線程(也就是我們實(shí)現(xiàn)了Runnable的類)的 run
方法的代碼塊,從而執(zhí)行我們自定義的邏輯。
還可以用線程池的方式調(diào)用
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("thread-pool-%d").build(); ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); singleThreadPool.execute(runnableThread); singleThreadPool.shutdown();
如果 Runnable
那么完美的話,就沒必要在 JDK1.5中加入和它超級(jí)相似的 Callable
了。
Runnable
有什么不完美的地方嗎?就是它的 run
方法是沒有返回值的。
如果你想在主線程中拿到新開啟線程的返回值的話,Runnable
就不太方便了。必須要借助共享變量來完成。
所以,如果你的場(chǎng)景是要有返回值的話, 就要 Callable
出手了。
Callable
Callable
是在 JDK1.5才加入的,為的就是彌補(bǔ) Runnable
沒有返回值的缺陷,雖然絕大多數(shù)場(chǎng)景都可以用 Runnable
來實(shí)現(xiàn)。
@FunctionalInterface public interface Callable<V> { V call() throws Exception; }
和 Runnable
類似的,@FunctionalInterface
也是后來加入的,可以不考慮,只是為了函數(shù)式寫法。
Callable
接口只有一個(gè) call
方法,并且有一個(gè)泛型返回值,可以返回任何類型。
舉個(gè)例子
首先聲明一個(gè)類,實(shí)現(xiàn)自 Callable
接口,返回值為字符串類型
public class CallableThread implements Callable<String> { @Override public String call() throws Exception { return "線程名稱:" + Thread.currentThread().getName(); } }
在代碼中通過下面的方式調(diào)用
CallableThread callableThread = new CallableThread(); FutureTask<String> futureTask = new FutureTask<>(callableThread); Thread thread = new Thread(futureTask); thread.start(); String result = futureTask.get(); System.out.println("執(zhí)行結(jié)果= " + result);
看上去就比 Runnable
要復(fù)雜一點(diǎn),要借助FutureTask
了,因?yàn)?Thread
類沒有接受Callable
的構(gòu)造函數(shù)。
使用 FutureTask.get()
方法獲取執(zhí)行結(jié)果。
在日常的開發(fā)中,不建議直接這樣用,除非你明確的知道這樣做沒有問題,否則的話,推薦使用線程池的方式來使用。
CallableThread callableThread = new CallableThread(); ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.submit(callableThread); String result = future.get(); System.out.println("任務(wù)執(zhí)行結(jié)果: " + result); executor.shutdown();
如何選擇用哪一個(gè)
取舍的基本原則就是需不需要返回值,如果不需要返回值,那直接就選 Runnable
,不用猶豫。如果有返回值的話,那更不用猶豫,不要想著借助共享變量的方式。
另外還有一點(diǎn)就是是否需要拋出異常, Runnable
是不接受拋出異常的,Callable
可以拋出異常。
Runnable
適合那種純異步的處理邏輯。比如每天定時(shí)計(jì)算報(bào)表,將報(bào)表存儲(chǔ)到數(shù)據(jù)庫或者其他地方,只是要計(jì)算,不需要馬上展示,展示內(nèi)容是在其他的方法中單獨(dú)獲取的。
比如那些非核心的功能,當(dāng)核心流程執(zhí)行完畢后,非核心功能就自己去執(zhí)行吧,至于成不成功的,不是特別重要。例如一個(gè)購物下單流程,下單、減庫存、加到用戶的訂單列表、扣款是核心功能,之后的發(fā)送APP通知、短信通知這些就啟動(dòng)新線程去干去吧。
最后
Runnable
在 java.lang
這個(gè)包下,而當(dāng)JDK1.5發(fā)布的時(shí)候,新加入的 Callable
被安置在了 java.util.concurrent
這個(gè)包下,這是 Java 里有名的并發(fā)編程相關(guān)包,各種鎖啊、多線程工具類啊,都被放在這個(gè)包下。按道理,Runnable
也應(yīng)該在這里才對(duì)。
可見再厲害的項(xiàng)目也是隨著項(xiàng)目的擴(kuò)大而慢慢的規(guī)劃,而前期的一些看似不太合理的地方,只能做兼容和妥協(xié)。
到此這篇關(guān)于Java中Runnable和Callable分別什么時(shí)候使用的文章就介紹到這了,更多相關(guān)Java Runnable Callable內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java使用多線程批次查詢大量數(shù)據(jù)(Callable返回?cái)?shù)據(jù))方式
- Java通過Callable實(shí)現(xiàn)多線程
- Java多線程中Callable和Future的解讀
- Java中的Callable實(shí)現(xiàn)多線程詳解
- Java使用Callable接口實(shí)現(xiàn)多線程的實(shí)例代碼
- Java多線程實(shí)現(xiàn)之Callable詳解
- Java中Runnable與Callable接口的區(qū)別詳解
- 詳解Java中Callable和Future的區(qū)別
- Java使用Runnable和Callable實(shí)現(xiàn)多線程的區(qū)別詳解
- java面試常問的Runnable和Callable的區(qū)別
- Java并發(fā)教程之Callable和Future接口詳解
- Java中callable的實(shí)現(xiàn)原理
相關(guān)文章
SpringBoot中使用EasyExcel并行導(dǎo)出多個(gè)excel文件并壓縮zip后下載的代碼詳解
SpringBoot的同步導(dǎo)出方式中,服務(wù)器會(huì)阻塞直到Excel文件生成完畢,在處理大量數(shù)據(jù)的導(dǎo)出功能,本文給大家介紹了SpringBoot中使用EasyExcel并行導(dǎo)出多個(gè)excel文件并壓縮zip后下載,需要的朋友可以參考下2024-09-09Eclipse+Webservice簡(jiǎn)單開發(fā)實(shí)例
這篇文章主要介紹了Eclipse+Webservice簡(jiǎn)單開發(fā)實(shí)例的相關(guān)資料,需要的朋友可以參考下2016-02-02簡(jiǎn)單了解Spring Web相關(guān)模塊運(yùn)行原理
這篇文章主要介紹了簡(jiǎn)單了解Spring Web相關(guān)模塊運(yùn)行原理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Spring Boot靜態(tài)資源路徑的配置與修改詳解
最近在做SpringBoot項(xiàng)目的時(shí)候遇到了“白頁”問題,通過查資料對(duì)SpringBoot訪問靜態(tài)資源做了總結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09Java動(dòng)態(tài)設(shè)置注解值及原理詳解
這篇文章主要介紹了Java動(dòng)態(tài)設(shè)置注解值及原理詳解,AnnotationInvocationHandler是注解的代理hander,通過反射獲取類的注解時(shí)會(huì)通過AnnotationInvocationHandler創(chuàng)建代理對(duì)象并將數(shù)據(jù)存儲(chǔ)到memberValues里,需要的朋友可以參考下2023-11-11SpringBoot+MyBatisPlus+MySQL8實(shí)現(xiàn)樹形結(jié)構(gòu)查詢
這篇文章主要為大家詳細(xì)介紹了SpringBoot+MyBatisPlus+MySQL8實(shí)現(xiàn)樹形結(jié)構(gòu)查詢,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06Java實(shí)現(xiàn)手機(jī)號(hào)碼歸屬地查詢
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)手機(jī)號(hào)碼歸屬地查詢功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12Jmeter如何獲取jtl文件中所有的請(qǐng)求報(bào)文詳解
JMeter的可以創(chuàng)建一個(gè)包含測(cè)試運(yùn)行結(jié)果的文本文件,這些通常稱為JTL文件,因?yàn)檫@是默認(rèn)擴(kuò)展名,但可以使用任何擴(kuò)展名,這篇文章主要給大家介紹了關(guān)于Jmeter如何獲取jtl文件中所有的請(qǐng)求報(bào)文的相關(guān)資料,需要的朋友可以參考下2021-09-09