FutureTask用法及解析


1 FutureTask概念

       FutureTask一個可取消的異步計算,FutureTask 實現了Future的基本方法,提空 start cancel 操作,可以查詢計算是否已經完成,並且可以獲取計算的結果。結果只可以在計算完成之后獲取,get方法會阻塞當計算沒有完成的時候,一旦計算已經完成,那么計算就不能再次啟動或是取消

一個FutureTask 可以用來包裝一個 Callable(一個有返回值的runnable) 或是一個runnable對象。因為FurtureTask實現了Runnable方法,所以一個 FutureTask可以提交(submit)給一個Excutor執行(excution).

2 FutureTask使用場景

       FutureTask可用於異步獲取執行結果或取消執行任務的場景。通過傳入Runnable或者Callable的任務給FutureTask,直接調用其run方法或者放入線程池執行,之后可以在外部通過FutureTask的get方法異步獲取執行結果,因此,FutureTask非常適合用於耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。另外,FutureTask還可以確保即使調用了多次run方法,它都只會執行一次Runnable或者Callable任務,或者通過cancel取消FutureTask的執行等。

請看下面代碼:

public class Memoizer2<A, V> implements Computable<A, V> {
	private final Map<A, V> cache = new ConcurrentHashMap<A, V>();
	private final Computable<A, V> c;

	public Memoizer2(Computable<A, V> c) {
		this.c = c;
	}

	public V compute(A arg) throws InterruptedException {
		V result = cache.get(arg);
		if (result == null) {
			/*
			 * 如果a線程進來,判斷為空,開始compute,因為compute的時間比較長,而且put操作是在compute后面
			 * 所以其它線程不能及時得得到信息,所以這時b、c線程因為result為空而走進來(注意,abc三線程的arg是
			 * 一樣的),於是乎b、c線程也執行compute方法,這樣就會造成大量的線程執行重復的操作而導致效率極低
			 */
			result = c.compute(arg);
			cache.put(arg, result);
		}
		return result;
	}
}

      針對上面的情況,我們可能會想,如果我們並不需要一定等到運算結果出來之后執行put,因為此時肯定有很多線程都因為result為null而執行重復的compute,所以我們想先快速定義一個對象並put,以該對象是否為null決定當前的arg是否運算過,這樣就可以減少線程執行重復運算的概率,於是FutureTask類為此而來!

      

public class Memoizer<A, V> implements Computable<A, V> {
	private final ConcurrentMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
	private final Computable<A, V> c;

	public Memoizer(Computable<A, V> c) {
		this.c = c;
	}

	public V compute(final A arg) throws InterruptedException {
		while (true) {
			Future<V> f = cache.get(arg);
			if (f == null) {
				/*
				 * 我們摒棄了之前通過判斷運算后的值是否為空來決定是否要執行運算,而是判斷future是否為null
				 * 為何?我們在判斷f為null后到put操作之間,執行的動作僅僅只是new了倆對象,new倆對象的時間
				 * 肯定比執行compute短。
				 * 過程:a線程進來,判斷f為null,new了callable和FutureTask,這時a線程立馬將future加進map
				 * , 因為這段代碼的速度很快,以至於b、c線程判斷f不為null,直接執行future.get(),但是此時future
				 * 不一定運算完,所以b、c得等一會,知道a運算完后獲取結果,這樣我們的b、c線程就不用花時間去
				 * 執行重復的操作了,直接使用a線程的結果即可!!
				 */
				Callable<V> eval = new Callable<V>() {
					public V call() throws InterruptedException {
						return c.compute(arg);
					}
				};
				FutureTask<V> ft = new FutureTask<V>(eval);
				/*
				 * 注意,這里我們執行的不是普通的put操作,而是"沒有則添加"的復合操作,不過這個方法已經提供了原子性
				 * 這樣我們的程序就更安全了!!
				 */
				f = cache.putIfAbsent(arg, ft);
				if (f == null) {
					f = ft;
					ft.run();
				}
			}
			try {
				return f.get();
			} catch (CancellationException e) {
				cache.remove(arg, f);
			} catch (ExecutionException e) {
				throw LaunderThrowable.launderThrowable(e.getCause());
			}
		}
	}
}

總結:

(1)FutureTask是類似於Runnable的一種存在,可以接受Callable這種帶返回結果的接口作構造參數

(2)FutureTask可以脫離主線程而單獨開線程去執行其他運算操作

(3)一個FutureTask不論被多少個線程執行run,都只會執行一次,執行一次后就保持在“運算完成”的狀態而不會回滾

(4)FutureTask可以保證:從運算線程返回的結果,可以安全的抵達調用運算線程的線程,中間不會出現線程安全問題

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM