Java多線程中的Callable和Future詳解
前言
創(chuàng)建線程的兩種方式,一種是直接繼承Thread,另外一種就是實(shí)現(xiàn)Runnable接口。
這兩種方式都有一個(gè)缺陷就是:在執(zhí)行完任務(wù)之后無(wú)法獲取執(zhí)行結(jié)果。
如果需要獲取執(zhí)行結(jié)果,就必須通過(guò)共享變量或者使用線程通信的方式來(lái)達(dá)到效果,這樣使用起來(lái)就比較麻煩。
而自從Java 1.5開(kāi)始,就提供了Callable和Future,通過(guò)它們可以在任務(wù)執(zhí)行完畢之后得到任務(wù)執(zhí)行結(jié)果。
一、Runnable接口
先看一下java.lang.Runnable吧,它是一個(gè)接口,在它里面只聲明了一個(gè)run()方法:
public interface Runnable { public abstract void run(); }
由于run()方法返回值為void類型,所以在執(zhí)行完任務(wù)之后無(wú)法返回任何結(jié)果。
二、Callable接口
Callable接口位于java.util.concurrent包下,在它里面也只聲明了一個(gè)方法,只不過(guò)這個(gè)方法叫做call()。
public interface Callable<V> { V call() throws Exception; }
可以看到,這是一個(gè)泛型接口,call()函數(shù)返回的類型就是傳遞進(jìn)來(lái)的V類型。
Callable接口可以看作是Runnable接口的補(bǔ)充,call方法帶有返回值,并且可以拋出異常。
三、FutureTask類
如何獲取Callable的返回結(jié)果呢?一般是通過(guò)FutureTask這個(gè)中間媒介來(lái)實(shí)現(xiàn)的。
整體的流程是這樣的:把Callable實(shí)例當(dāng)作參數(shù),生成一個(gè)FutureTask的對(duì)象,然后把這個(gè)對(duì)象當(dāng)作一個(gè)Runnable,作為參數(shù)另起線程。
3.1 FutureTask的結(jié)構(gòu)
3.2 FutureTask的啟動(dòng)
由于FutureTask實(shí)現(xiàn)了Runnable,因此它既可以通過(guò)Thread包裝來(lái)直接執(zhí)行,也可以提交給ExecuteService來(lái)執(zhí)行。
下面以Thread包裝線程方式啟動(dòng)來(lái)說(shuō)明一下。
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Demo { public static void main(String[] args) throws Exception { Callable<Integer> call = new Callable<Integer>() { public Integer call() throws Exception { System.out.println("計(jì)算線程正在計(jì)算結(jié)果..."); Thread.sleep(3000); return 1; } }; FutureTask<Integer> task = new FutureTask<>(call); Thread thread = new Thread(task); thread.start(); System.out.println("main線程干點(diǎn)別的..."); Integer result = task.get(); System.out.println("從計(jì)算線程拿到的結(jié)果為:" + result); } }
四、Future接口
FutureTask繼承體系中的核心接口是Future。
Future的核心思想是:一個(gè)方法,計(jì)算過(guò)程可能非常耗時(shí),等待方法返回,顯然不明智??梢栽谡{(diào)用方法的時(shí)候,立馬返回一個(gè)Future,可以通過(guò)Future這個(gè)數(shù)據(jù)結(jié)構(gòu)去控制方法f的計(jì)算過(guò)程。
這里的控制包括:
- get方法:獲取計(jì)算結(jié)果(如果還沒(méi)計(jì)算完,也是必須等待的)
- cancel方法:還沒(méi)計(jì)算完,可以取消計(jì)算過(guò)程
- isDone方法:判斷是否計(jì)算完
- isCancelled方法:判斷計(jì)算是否被取消
補(bǔ)充:同樣是獲取線程的計(jì)算結(jié)果,Java則顯得很繁瑣,而C語(yǔ)言的實(shí)現(xiàn)則簡(jiǎn)單的多。
看下面的例子
假設(shè)有兩個(gè)函數(shù):
void * dose_do(void * a) { for (int i = 0; i < 5; i++) { sleep(1); puts("does_do"); } return NULL; } void * dose_not(void * a) { for (int i = 0; i < 5; i++) { sleep(1); puts("does_not"); } return NULL; }
這兩個(gè)函數(shù)都返回了void指針,因?yàn)関oid指針可以指向存儲(chǔ)器中任何數(shù)據(jù)類型的數(shù)據(jù),線程函數(shù)的返回類必須是void *。
1、創(chuàng)建線程
創(chuàng)建線程可以使用多種線程庫(kù),在此我們使用最流行的一種:POSIX線程庫(kù),也叫pthread。
必須包含#include <pthread.h>頭文件。
我們使用pthread_create() 函數(shù)創(chuàng)建并運(yùn)行一個(gè)線程,而且每個(gè)線程都需要把線程信息保存在一個(gè)pthread_t類型的數(shù)據(jù)中。
// 線程對(duì)象 pthread_t t0; pthread_t t1; if (pthread_create(&t0, NULL, dose_not, NULL) == -1) { error("無(wú)法創(chuàng)建線程t0"); } if (pthread_create(&t1, NULL, dose_do, NULL) == -1) { error("無(wú)法創(chuàng)建線程t1"); }
2、獲取線程返回值
上邊的兩個(gè)函數(shù)將會(huì)獨(dú)立的在線程中運(yùn)行直到結(jié)束,但是我們需要知道這兩個(gè)函數(shù)什么時(shí)候結(jié)束??梢允褂胮thread_join()函數(shù)等待函數(shù)結(jié)束,它會(huì)接受線程函數(shù)的返回值,并保存在一個(gè)void *類型的數(shù)據(jù)中。那么這個(gè)函數(shù)是如何得知線程結(jié)束的呢?當(dāng)?shù)玫骄€程函數(shù)的返回值的時(shí)候,就表明線程函數(shù)結(jié)束了。這也是為什么線程函數(shù)必須要有返回值的原因。
void *result; if (pthread_join(t0, &result) == -1) { error("無(wú)法回收線程t0"); } if (pthread_join(t1, &result) == -1) { error("無(wú)法回收線程t1"); }
我們來(lái)看全部代碼:
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> // 錯(cuò)誤處理函數(shù) void error(char *msg) { fprintf(stderr, "Error: %s %s", msg, strerror(errno)); exit(1); } void * dose_not(void * a) { for (int i = 0; i < 5; i++) { sleep(1); puts("does_not"); } return NULL; } void * dose_do(void * a) { for (int i = 0; i < 5; i++) { sleep(1); puts("does_do"); } return NULL; } int main(int argc, const char * argv[]) { // 線程對(duì)象 pthread_t t0; pthread_t t1; if (pthread_create(&t0, NULL, dose_not, NULL) == -1) { error("無(wú)法創(chuàng)建線程t0"); } if (pthread_create(&t1, NULL, dose_do, NULL) == -1) { error("無(wú)法創(chuàng)建線程t1"); } void *result; if (pthread_join(t0, &result) == -1) { error("無(wú)法回收線程t0"); } if (pthread_join(t1, &result) == -1) { error("無(wú)法回收線程t1"); } return 0; }
到此這篇關(guān)于Java多線程中的Callable和Future詳解的文章就介紹到這了,更多相關(guān)Java多線程Callable和Future內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java easyexcel導(dǎo)出報(bào)內(nèi)存溢出的問(wèn)題解決
在Java開(kāi)發(fā)時(shí),使用EasyExcel處理大數(shù)據(jù)量導(dǎo)出可能遇到內(nèi)存溢出問(wèn)題,本文深入分析了內(nèi)存溢出的原因,并提出了優(yōu)化策略,感興趣的可以了解一下2024-10-10SSH框架網(wǎng)上商城項(xiàng)目第20戰(zhàn)之在線支付平臺(tái)
這篇文章主要為大家詳細(xì)介紹了SSH框架網(wǎng)上商城項(xiàng)目第20戰(zhàn)之在線支付平臺(tái),關(guān)于第三方支付的內(nèi)容從本文開(kāi)始,感興趣的小伙伴們可以參考一下2016-06-06Java 數(shù)據(jù)結(jié)構(gòu)哈希算法之哈希桶方式解決哈希沖突
實(shí)際上哈希桶是解決哈希表沖突的一種方法。常見(jiàn)的解決沖突的兩種方法:分離鏈接法、開(kāi)放定址法。其中使用分離鏈接法,得到的對(duì)應(yīng)關(guān)系即為哈希桶2022-02-02Mybatis-Plus分頁(yè)的使用與注意事項(xiàng)
分頁(yè)查詢每個(gè)人程序猿幾乎都使用過(guò),下面這篇文章主要給大家介紹了關(guān)于Mybatis-Plus分頁(yè)的使用與注意事項(xiàng)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04Intellij IDEA中一次性折疊所有Java代碼的快捷鍵設(shè)置
這篇文章主要介紹了Intellij IDEA中一次性折疊所有Java代碼的快捷鍵設(shè)置,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05java Quartz定時(shí)器任務(wù)與Spring task定時(shí)的幾種實(shí)現(xiàn)方法
本篇文章主要介紹了java Quartz定時(shí)器任務(wù)與Spring task定時(shí)的幾種實(shí)現(xiàn)方法的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02深入理解Java中的final關(guān)鍵字_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Java中的final關(guān)鍵字非常重要,它可以應(yīng)用于類、方法以及變量。這篇文章中我將帶你看看什么是final關(guān)鍵字以及使用final的好處,具體內(nèi)容詳情通過(guò)本文學(xué)習(xí)吧2017-04-04