Java異步編程Future應(yīng)用方式
1 Future接口介紹
此時有的人會說,對于任務(wù)并行需求,直接通過多線程實現(xiàn)不就可以了, 要注意,對于多線程的實現(xiàn),java提供了三種方式:繼承Thread類、實現(xiàn)Runnable接口和實現(xiàn)Callable接口。
但是業(yè)務(wù)代碼在執(zhí)行時會考慮執(zhí)行順序的問題,直接基于這些方式實現(xiàn)多線程會出現(xiàn)兩個問題:
- 1)要想控制線程執(zhí)行順序,會通過join()等待線程結(jié)束,那這樣的話又回歸到了阻塞式調(diào)用的思路上,違背了并行的需求。 另外還可以通過wait()、notify()、notifyAll()結(jié)合狀態(tài)變量實現(xiàn),但實現(xiàn)起來過于復(fù)雜。
- 2)線程執(zhí)行完之后,要想獲取線程執(zhí)行結(jié)果,還要用過共享變量或線程間通信等方式來獲取,同樣過于復(fù)雜。為了解決上述問題,Java5中推出了Future,其初衷就是用于構(gòu)建復(fù)雜并行操作。內(nèi)部方法在返回時,不是返回一個值,而是返回Future對象。其本質(zhì)是在執(zhí)行主業(yè)務(wù)的同時,異步的執(zhí)行其他分業(yè)務(wù),從而利用原本需要同步執(zhí)行時的等待時間去執(zhí)行其他的業(yè)務(wù),當需要獲取其結(jié)果時,再進行獲取。
Java官網(wǎng)對于Future的描述:

Future表示異步計算的結(jié)果。 提供了一些方法來檢查計算是否完成,等待其完成以及檢索計算結(jié)果。 只有在計算完成后才可以使用get方法檢索結(jié)果,必要時將其阻塞,直到準備就緒為止。 取消通過cancel方法執(zhí)行。 提供了其他方法來確定任務(wù)是正常完成還是被取消。 一旦計算完成,就不能取消計算。

在Future接口中有五個抽象方法:

cancel():取消任務(wù), 取消成功返回true;入?yún)ayInterruptIfRunning表示是否允許取消正在執(zhí)行中的任務(wù)。

isCancelled():返回布爾值,代表是否取消成功。

isDone():返回布爾值,代表是否執(zhí)行完畢。

get():返回Future對象,獲取執(zhí)行結(jié)果,如果任務(wù)沒有完成會阻塞到任務(wù)完成再返回。
2 Future應(yīng)用
Future的使用通常需要配合ExecutorService和Callable一起
使用,使用示例如下:
public class FutureAsyncDemo {
static Random random = new Random();
static ExecutorService executor =
Executors.newCachedThreadPool();
//接收文章名稱,獲取并計算文章分數(shù)
public static int getArticleScore(String
aname){
Future<Integer> futureA =
executor.submit(new
CalculateArticleScoreA());
Future<Integer> futureB =
executor.submit(new
CalculateArticleScoreA());
Future<Integer> futureC =
executor.submit(new
CalculateArticleScoreA());
doSomeThingElse();
Integer a = null;
try {
a = futureA.get();
} catch (InterruptedException e) {
futureA.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureA.cancel(true);
e.printStackTrace();
}
Integer b = null;
try {
b = futureB.get();
} catch (InterruptedException e) {
futureB.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureB.cancel(true);
e.printStackTrace();
}
Integer c = null;
try {
c = futureC.get();
} catch (InterruptedException e) {
futureC.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureC.cancel(true);
e.printStackTrace();
}
executor.shutdown();
return a+b+c;
}
private static void doSomeThingElse() {
System.out.println("exec other
things");
}
public static void main(String[] args) {
System.out.println(getArticleScore("demo"))
;
}
}
class CalculateArticleScoreA implements
Callable<Integer>{
@Override
public Integer call() throws Exception {
//業(yè)務(wù)代碼
Random random = new Random();
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().g
etName());
return random.nextInt(100);
}
}執(zhí)行結(jié)果
exec other things
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
159
上述方法改造了calculateArticleScore(),在其內(nèi)部基于線程池調(diào)用重寫了Callable接口中的call(),并在call()中對具體業(yè)務(wù)完成編碼,并且讓其在執(zhí)行時睡三秒鐘。根據(jù)結(jié)果可以看到,先調(diào)用了計算文章分數(shù)方法,其內(nèi)部開啟了子線程去執(zhí)行任務(wù),并且子線程在執(zhí)行時,并沒有阻塞主線程的執(zhí)行。
當主線程需要結(jié)果時,在通過返回的Future來獲取子任務(wù)中的返回值。
3 Future并行變串行問題解析
剛才已經(jīng)基于Future演示了并行執(zhí)行的效果,已經(jīng)達到了期望,但是在使用的過程中,其實還有個坑需要說明。
對于Future的使用,如稍加不注意,就會讓并行變?yōu)榇小?/p>
示例代碼如下:
public class FutureAsyncDemo {
static ExecutorService executor =
Executors.newCachedThreadPool();
//接收文章名稱,獲取并計算文章分數(shù)
public static int getArticleScore(String
aname){
Future<Integer> futureA =
executor.submit(new
CalculateArticleScoreA());
Future<Integer> futureB =
executor.submit(new
CalculateArticleScoreB());
Future<Integer> futureC =
executor.submit(new
CalculateArticleScoreC());
doSomeThingElse();
Integer a = 0;
try {
a = futureA.get();
} catch (InterruptedException e) {
futureA.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureA.cancel(true);
e.printStackTrace();
}
Integer b = 0;
try {
b = futureB.get();
} catch (InterruptedException e) {
futureB.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureB.cancel(true);
e.printStackTrace();
}
Integer c = 0;
try {
c = futureC.get();
} catch (InterruptedException e) {
futureC.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureC.cancel(true);
e.printStackTrace();
}
executor.shutdown();
return a+b+c;
}
private static void doSomeThingElse() {
System.out.println("exec other
things");
}
public static void main(String[] args) {
System.out.println(getArticleScore("demo"))
;
}
}
class CalculateArticleScoreA implements
Callable<Integer>{
@Override
public Integer call() throws Exception {
Random random = new Random();
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().g
etName());
return random.nextInt(100);
}
}
class CalculateArticleScoreB implements
Callable<Integer>{
@Override
public Integer call() throws Exception {
Random random = new Random();
TimeUnit.SECONDS.sleep(20);
System.out.println(Thread.currentThread().g
etName());
return random.nextInt(100);
}
}
class CalculateArticleScoreC implements
Callable<Integer>{
@Override
public Integer call() throws Exception {
Random random = new Random();
TimeUnit.SECONDS.sleep(30);
System.out.println(Thread.currentThread().g
etName());
return random.nextInt(100);
}
}上述代碼加計算得分方法復(fù)制出來兩份,各自休眠10秒、20秒、30秒。當方法返回Future之后,調(diào)用get()進行值獲取時,發(fā)現(xiàn)每次調(diào)用時都需要進行等待。
這樣可以發(fā)現(xiàn),之前的并行現(xiàn)在變成了串行了?。。。?這個問題為什么會產(chǎn)生呢?需要看一下Future中對于get()的介紹

根據(jù)源碼可知,當調(diào)用get()時,其會等待對應(yīng)方法執(zhí)行完畢后,才會返回結(jié)果,否則會一直等待。因為這個設(shè)定,所以上述代碼則出現(xiàn)并行變串行的效果。
對于這個問題的解決,可以調(diào)用get()的重載,get(longtimeout, TimeUnit unit)。設(shè)置等待的時長,如果超時則拋出TimeoutException。
使用示例如下:
public class FutureAsyncDemo {
static Random random = new Random();
static ExecutorService executor =
Executors.newCachedThreadPool();
//接收文章名稱,獲取并計算文章分數(shù)
public static int
getArticleScore(String aname){
Future<Integer> futureA =
executor.submit(new
CalculateArticleScoreA());
Future<Integer> futureB =
executor.submit(new
CalculateArticleScoreB());
Future<Integer> futureC =
executor.submit(new
CalculateArticleScoreC());
doSomeThingElse();
Integer a = 0;
try {
a = futureA.get();
} catch (InterruptedException e) {
futureA.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureA.cancel(true);
e.printStackTrace();
}
Integer b = 0;
try {
b = futureB.get(3L,
TimeUnit.SECONDS);
} catch (TimeoutException e) {
e.printStackTrace();
}
catch (InterruptedException e) {
futureB.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureB.cancel(true);
e.printStackTrace();
}
Integer c = 0;
try {
c = futureC.get();
} catch (InterruptedException e) {
futureC.cancel(true);
e.printStackTrace();
} catch (ExecutionException e) {
futureC.cancel(true);
e.printStackTrace();
}
executor.shutdown();
return a+b+c;
}
private static void doSomeThingElse() {
System.out.println("exec other
things");
}
public static void main(String[] args)
{
System.out.println(getArticleScore("demo")
);
}
}
class CalculateArticleScoreA implements
Callable<Integer>{
@Override
public Integer call() throws Exception
{
Random random = new Random();
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().
getName());
return random.nextInt(100);
}
}
class CalculateArticleScoreB implements
Callable<Integer>{
@Override
public Integer call() throws Exception
{
Random random = new Random();
TimeUnit.SECONDS.sleep(20);
System.out.println(Thread.currentThread().
getName());
return random.nextInt(100);
}
}
class CalculateArticleScoreC implements
Callable<Integer>{
@Override
public Integer call() throws Exception
{
Random random = new Random();
TimeUnit.SECONDS.sleep(30);
System.out.println(Thread.currentThread().
getName());
return random.nextInt(100);
}
}在上述方法中,對于B的get()設(shè)置了超時時間三秒鐘,如果當調(diào)用其獲取返回值時,如果超過三秒仍然沒有返回結(jié)果,則拋出超時異常,接著方法會再次向下運行。
對于Future來說,它能夠支持任務(wù)并發(fā)執(zhí)行,對于任務(wù)結(jié)果的獲取順序是按照提交的順序獲取,在使用的過程中建議通過CPU高速輪詢的方式獲取任務(wù)結(jié)果,但這種方式比較耗費資源。不建議使用
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于mybatis-plus-generator的簡單使用示例詳解
在springboot項目中集成mybatis-plus是很方便開發(fā)的,最近看了一下plus的文檔,簡單用一下它的代碼生成器,接下來通過實例代碼講解關(guān)于mybatis-plus-generator的簡單使用,感興趣的朋友跟隨小編一起看看吧2024-03-03
MyBatis Plus 實現(xiàn)多表分頁查詢功能的示例代碼
這篇文章主要介紹了MyBatis Plus 實現(xiàn)多表分頁查詢功能,本文給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
SpringBoot集成Aviator實現(xiàn)參數(shù)校驗的示例代碼
在實際開發(fā)中,參數(shù)校驗是保障系統(tǒng)穩(wěn)定和數(shù)據(jù)可靠性的重要措施,Aviator 是一個高性能的表達式引擎,它能夠簡化復(fù)雜的邏輯判斷并提升參數(shù)校驗的靈活性,本文將介紹如何在 Spring Boot 中集成 Aviator,并利用它來實現(xiàn)靈活的參數(shù)校驗,需要的朋友可以參考下2025-02-02
基于@MapperScan和@ComponentScan的使用區(qū)別
這篇文章主要介紹了@MapperScan和@ComponentScan的使用區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Java中Pattern.compile函數(shù)的使用詳解
這篇文章主要介紹了Java中Pattern.compile函數(shù)的使用詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

