Java中Future和FutureTask的示例詳解及使用
一、Future 接口
當 call()方法完成時,結(jié)果必須存儲在主線程已知的對象中,以便主線程可以知道該線程返回的結(jié)果。為此,可以使用 Future 對象。
將 Future 視為保存結(jié)果的對象–它可能暫時不保存結(jié)果,但將來會保存(一旦Callable 返回)。Future 基本上是主線程可以跟蹤進度以及其他線程的結(jié)果的一種方式。要實現(xiàn)此接口,必須重寫 5 種方法,這里列出了重要的方法,如下:
public boolean isDone()

public boolean cancel(boolean mayInterruptIfRunning)
用于停止任務(wù)。如果尚未啟動,它將停止任務(wù)。如果已啟動,則僅在 mayInterrupt 為 true時才會中斷任務(wù)。

boolean isCancelled()
如果任務(wù)在正常結(jié)束之前被取消返回true
public V get() throws InterruptedException, ExecutionException
用于獲取任務(wù)的結(jié)果。如果任務(wù)完成,它將立即返回結(jié)果,否則將等待任務(wù)完成,然后返回結(jié)果。
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException
如果任務(wù)完成,則返回 true,否則返回 false。
Callable 與 Runnable 類似,因為它封裝了要在另一個線程上運行的任務(wù),而 Future 用于存儲從另一個線程獲得的結(jié)果。
實際上,F(xiàn)uture 也可以與 Runnable 一起使用。要創(chuàng)建線程,需要 Runnable。為了獲得結(jié)果,需要 future。
二、FutureTask
介紹:當一個線程需要等待另一個線程把某個任務(wù)執(zhí)行完后它才能繼續(xù)執(zhí)行,此時可以使用FutureTask。假設(shè)有多個線程執(zhí)行若干任務(wù),每個任務(wù)最多只能被執(zhí)行一次。當多個線程試圖同時執(zhí)行同一個任務(wù)時,只允許一個線程執(zhí)行任務(wù),其他線程需要等待這個任務(wù)執(zhí)行完后才能繼續(xù)執(zhí)行。
Java 庫具有具體的 FutureTask 類型,該類型實現(xiàn) Runnable 和 Future,并方便地將兩種功能組合在一起。 可以通過為其構(gòu)造函數(shù)提供 Callable 來創(chuàng)建FutureTask。然后,將 FutureTask 對象提供給 Thread 的構(gòu)造函數(shù)以創(chuàng)建Thread 對象。因此,間接地使用 Callable 創(chuàng)建線程。
FutureTask狀態(tài)轉(zhuǎn)換
FutureTask有以下7種狀態(tài):
FutureTask任務(wù)的運行狀態(tài),最初為NEW。運行狀態(tài)僅在set、setException和cancel方法中轉(zhuǎn)換為終端狀態(tài)。在完成過程中,狀態(tài)可能呈現(xiàn)出瞬時值INTERRUPTING(僅在中斷運行程序以滿足**cancel(true)**的情況下)或者COMPLETING(在設(shè)置結(jié)果時)狀態(tài)時。從這些中間狀態(tài)到最終狀態(tài)的轉(zhuǎn)換使用成本更低的有序/延遲寫,因為值是統(tǒng)一的,需要進一步修改。
state:表示當前任務(wù)的運行狀態(tài),F(xiàn)utureTask的所有方法都是圍繞state開展的,state聲明為volatile,保證了state的可見性,當對state進行修改時所有的線程都會看到。
NEW:表示一個新的任務(wù),初始狀態(tài)
COMPLETING:當任務(wù)被設(shè)置結(jié)果時,處于COMPLETING狀態(tài),這是一個中間狀態(tài)。
NORMAL:表示任務(wù)正常結(jié)束。
EXCEPTIONAL:表示任務(wù)因異常而結(jié)束
CANCELLED:任務(wù)還未執(zhí)行之前就調(diào)用了cancel(true)方法,任務(wù)處于CANCELLED
INTERRUPTING:當任務(wù)調(diào)用cancel(true)中斷程序時,任務(wù)處于INTERRUPTING狀態(tài),這是一個中間狀態(tài)。
INTERRUPTED:任務(wù)調(diào)用cancel(true)中斷程序時會調(diào)用interrupt()方法中斷線程運行,任務(wù)狀態(tài)由INTERRUPTING轉(zhuǎn)變?yōu)镮NTERRUPTED
可能的狀態(tài)過渡:
1、NEW -> COMPLETING -> NORMAL:正常結(jié)束
2、NEW -> COMPLETING -> EXCEPTIONAL:異常結(jié)束
3、NEW -> CANCELLED:任務(wù)被取消
4、NEW -> INTERRUPTING -> INTERRUPTED:任務(wù)出現(xiàn)中斷
三、使用 Callable 和 Future
Runnable缺少的一項功能是,當線程終止時(即 run()完成時),我們無法使線程返回結(jié)果。
為了支持此功能,Java 中提供了 Callable 接口。不能直接替換 runnable,因為 Thread 類的構(gòu)造方法根本沒有 Callable。
所以我們可以找一個中間人,也就是FutureTask。


案例
class MyThreadA implements Callable {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName() + "在call方法里");
System.out.println(Thread.currentThread().getName() + "線程進入了 call方法,開始睡覺(進行了一些計算)");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + "睡醒了");
return Thread.currentThread().getName() + "返回的:" + System.currentTimeMillis();
}
}
public class demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTaskA = new FutureTask<>(new MyThreadA());
FutureTask<String> futureTaskB = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName() + "在call方法里");
return Thread.currentThread().getName() + "返回的:" + System.currentTimeMillis();
});
new Thread(futureTaskA,"線程A").start();
new Thread(futureTaskB,"線程B").start();
while (!futureTaskB.isDone()){ //isDone表示FutureTask的計算是否完成
System.out.println("wait.......");
}
System.out.println(futureTaskA.get());
System.out.println(futureTaskB.get());
System.out.println(Thread.currentThread().getName() + "結(jié)束了");
}
}
輸出結(jié)果:

由上圖兩個線程返回的時間差約等于10秒可以看出,當一個線程(線程B)需要等待(一直wait…)另一個線程(線程A)把某個任務(wù)(進行了一些計算)執(zhí)行完后它才能繼續(xù)執(zhí)行,此時可以使用FutureTask。不管futureTaskA.get()和futureTaskB.get()誰在前面,輸出結(jié)果一定是“線程B返回的:xxx”在“wait…”的后面。假設(shè)有多個線程執(zhí)行若干任務(wù),每個任務(wù)最多只能被執(zhí)行一次。當多個線程試圖同時執(zhí)行同一個任務(wù)時,只允許一個線程執(zhí)行任務(wù),其他線程需要等待這個任務(wù)執(zhí)行完后才能繼續(xù)執(zhí)行。
四、小結(jié)(FutureTask核心原理)
FutureTask核心原理
在主線程中需要執(zhí)行比較耗時的操作時,但又不想阻塞主線程時,可以把這些作業(yè)交給 Future 對象在后臺完成,當主線程將來需要時,就可以通過 Future對象獲得后臺作業(yè)的計算結(jié)果或者執(zhí)行狀態(tài)。
? 一般 FutureTask 多用于耗時的計算,主線程可以在完成自己的任務(wù)后,再去獲取結(jié)果
? 僅在計算完成時才能檢索結(jié)果;如果計算尚未完成,則阻塞 get 方法。一旦計算完成,就不能再重新開始或取消計算。get 方法而獲取結(jié)果只有在計算完成時獲取,否則會一直阻塞直到任務(wù)轉(zhuǎn)入完成狀態(tài),然后會返回結(jié)果或者拋出異常。
? get只計算一次,因此 get 方法放到最后。
附:FutureTask在高并發(fā)環(huán)境下確保任務(wù)只執(zhí)行一次
網(wǎng)上有篇例子,但是中間講的不是很清楚。我重新梳理了一下。
在很多高并發(fā)的環(huán)境下,往往我們只需要某些任務(wù)只執(zhí)行一次。這種使用情景FutureTask的特性恰能勝任。舉一個例子,假設(shè)有一個帶key的連接池,當key存在時,即直接返回key對應(yīng)的對象;當key不存在時,則創(chuàng)建連接。對于這樣的應(yīng)用場景,通常采用的方法為使用一個Map對象來存儲key和連接池對應(yīng)的對應(yīng)關(guān)系,典型的代碼如下面所示:
package com.concurrency.chapter15;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: 錯誤示例
*
* @description: 在很多高并發(fā)的環(huán)境下,往往我們只需要某些任務(wù)只執(zhí)行一次。
* 這種使用情景FutureTask的特性恰能勝任。舉一個例子,假設(shè)有一個帶key的連接池,
* 當key存在時,即直接返回key對應(yīng)的對象;當key不存在時,則創(chuàng)建連接。對于這樣的應(yīng)用場景,
* 通常采用的方法為使用一個Map對象來存儲key和連接池對應(yīng)的對應(yīng)關(guān)系,典型的代碼如下
* 在例子中,我們通過加鎖確保高并發(fā)環(huán)境下的線程安全,也確保了connection只創(chuàng)建一次,然而卻犧牲了性能。
*
* @author: zhouzhixiang
*
* @create: 2019-05-14 20:22
*/
public class FutureTaskConnection1 {
private static Map<String, Connection> connectionPool = new HashMap<>();
private static ReentrantLock lock = new ReentrantLock();
public static Connection getConnection(String key) {
try {
lock.lock();
Connection connection = connectionPool.get(key);
if (connection == null) {
Connection newConnection = createConnection();
connectionPool.put(key, newConnection);
return newConnection;
}
return connection;
} finally {
lock.unlock();
}
}
private static Connection createConnection() {
try {
return DriverManager.getConnection("");
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
總結(jié)
到此這篇關(guān)于Java中Future和FutureTask的文章就介紹到這了,更多相關(guān)Java?Future和FutureTask使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript與jsp發(fā)送請求到servlet的幾種方式實例
本文分別給出了javascript發(fā)送請求到servlet的5種方式實例與 jsp發(fā)送請求到servlet的6種方式實例2018-03-03
攔截器獲取request的值之后,Controller拿不到值的解決
這篇文章主要介紹了攔截器獲取request的值之后,Controller拿不到值的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
使用java + selenium + OpenCV破解騰訊防水墻滑動驗證碼功能
這篇文章主要介紹了使用java + selenium + OpenCV破解騰訊防水墻滑動驗證碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11

