future.get方法阻塞問題的解決,實現按照任務完成的先後順序獲取任務的結果

1、Future

Future模式是多線程設計常用的一種設計模式。Future模式可以理解成:我有一個任務,提交給了Future,Future替我完成這個任務。期間我自己可以去做任何想做的事情。一段時間之後,我就便可以從Future那兒取出結果。
Future提供了三種功能:
判斷任務是否完成
能夠中斷任務
能夠獲取任務執行的結果
向線程池中提交任務的submit方法不是阻塞方法,而Future.get方法是一個阻塞方法,當submit提交多個任務時,只有所有任務都完成後,才能使用get按照任務的提交順序得到返回結果,所以一般需要使用future.isDone先判斷任務是否全部執行完成,完成後再使用future.get得到結果。(也可以用get (long timeout, TimeUnit unit)方法可以設置超時時間,防止無限時間的等待)
三段式的編程:1.啓動多線程任務2.處理其他事3.收集多線程任務結果,Future雖然可以實現獲取異步執行結果的需求,但是它沒有提供通知的機制,要麼使用阻塞,在future.get()的地方等待future返回的結果,這時又變成同步操作;要麼使用isDone()輪詢地判斷Future是否完成,這樣會耗費CPU的資源。
解決方法:CompletionService和CompletableFuture(按照任務完成的先後順序獲取任務的結果

2、CompletionService是java1.8之前最好用的方法,

能夠實現按照任務完成的先後順序獲取任務的結果。

public class TestCompletionService {
	private static final String commandstr01 = "hahah";
	private static final String commandstr02 = "hahah";
	
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//1、創建一個線程池
		ExecutorService executorService = Executors.newCachedThreadPool();
		
		CompletionService<String> completionService = new ExecutorCompletionService<String>(executorService);
		
		completionService.submit(new MyThreadt33(commandstr01));
		completionService.submit(new MyThreadt44(commandstr01));
			
		executorService.shutdown();
		
		System.out.println(completionService.take().get());
		System.out.println(completionService.take().get());
	}
}

class MyThreadt33 implements Callable<String>{
	private String commandstr;          // 要運行的mingling
	public MyThreadt33(String commandstr) {
		this.commandstr = commandstr;
	}
	@Override
	public String call() throws Exception {
		int sum = 0;
		for (int i = 0; i < 100; i++) {
			Thread.sleep(200);
			sum += i;
			System.out.println("Mythread3: "+i);
		}
		return String.valueOf(sum+300000);
	}
}

class MyThreadt44 implements Callable<String>{
	private String commandstr;          // 要運行的mingling
	public MyThreadt44(String commandstr) {
		this.commandstr = commandstr;
	}
	@Override
	public String call() throws Exception {
		int sum = 0;
		for (int i = 0; i < 50; i++) {
			Thread.sleep(200);
			sum += i;
			System.out.println("Mythread4: "+i);
		}
		return String.valueOf(sum+400000);
	}
}

CompletionService方法可以通過completionService.take().get()方法獲取最快完成的線程的返回結果(若當前沒有線程執行完成則阻塞直到最快的線程執行結束),第二次調用則返回第二快完成的線程的返回結果。

3、CompletableFuture接口

所謂異步調用其實就是實現一個可無需等待被調函數的返回值而讓操作繼續運行的方法。簡單的講就是另啓一個線程來完成調用中的部分計算,使調用繼續運行或返回,而不需要等待計算結果。但調用者仍需要取線程的計算結果。

JDK1.5新增了Future接口,用於描述一個異步計算的結果。雖然 Future 以及相關使用方法提供了異步執行任務的能力,但是對於結果的獲取卻是很不方便,只能通過阻塞或者輪詢的方式得到任務的結果。

JDK1.8後提出了CompletableFuture接口實現了Future和CompletionStage兩個接口,CompletionStage可以看做是一個異步任務執行過程的抽象(CompletionStage代表異步計算過程中的某一個階段,一個階段完成以後可能會觸發另外一個階段,一個階段的計算執行可以是一個Function,Consumer或者Runnable。比如:

stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println()))

我們可以基於CompletableFuture創建任務和鏈式處理多個任務,並實現按照任務完成的先後順序獲取任務的結果。

(1)創建任務

##使用runAsync方法新建一個線程來運行Runnable對象(無返回值)

##使用supplyAysnc方法新建線程來運行Supplier<T>對象(有返回值)

##基於線程池創建

(2)任務的異步處理

不論Future.get()方法還是CompletableFuture.get()方法都是阻塞的,爲了獲取任務的結果同時不阻塞當前線程的執行,我們可以使用CompletionStage提供的方法結合callback來實現任務的異步處理。

##whenComplete:是執行當前任務的線程執行繼續執行 whenComplete 的任務。
##whenCompleteAsync:把 whenCompleteAsync 這個任務繼續提交給線程池來進行執行,也就是並行執行。

##thenApply:當一個線程依賴另一個線程時,可以使用 thenApply 方法來把這兩個線程串行化

##thenAcceptthenAccept接收上一階段的輸出作爲本階段的輸入,並消費處理,無返回結果。 

##thenRun:不關心前一階段的計算結果,因爲它不需要輸入參數,進行消費處理,無返回結果。

## thenCombine:會把兩個 CompletionStage 的任務都執行完成後,把兩個任務的結果一塊交給 thenCombine 來處理。

## applyToEither :兩個CompletionStage,誰執行返回的結果快,我就用那個CompletionStage的結果進行下一步的轉化操作。

##acceptEither 方法:兩個CompletionStage,誰執行返回的結果快,我就用那個CompletionStage的結果進行下一步的消耗操作

public class TestCompletableFuture {
	private static final String commandstr01 = "hahah";
	private static final String commandstr02 = "hahah";
	private static final String commandstr03 = "hahah";
	private static final String commandstr04 = "hahah";

	    public static void main(String[] args) throws InterruptedException, ExecutionException{
	        
	    	ExecutorService executorService = Executors.newCachedThreadPool();
	    	
	        CompletableFuture.supplyAsync(new MyThreadt444(commandstr02),executorService).whenComplete((result, e) -> {
	        	//執行線程執行完以後的操作。
	            System.out.println(result + " " + e);
	        }).exceptionally((e) -> {
	            //拋出異常
	        	System.out.println("exception " + e);
	            return "exception";
	        });
	        
	         CompletableFuture.supplyAsync(new MyThreadt333(commandstr02),executorService).whenComplete((result, e) -> {
	        	//執行線程執行完以後的操作。
	        	System.out.println(result + " " + e);
	        }).exceptionally((e) -> {
	            System.out.println("exception " + e);
	            return "exception";
	        });
	    }
}



class MyThreadt333 implements Supplier<String>{

	private String commandstr;          // 要運行的mingling
	public MyThreadt333(String commandstr) {
		this.commandstr = commandstr;
	}
	@Override
	public String get() {
		int sum = 0;
		for (int i = 0; i < 30; i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			sum += i;
			System.out.println("Mythread333: "+i);
		}
		return String.valueOf(sum+300000);
	}
}

class MyThreadt444 implements Supplier<String>{

	private String commandstr;          // 要運行的mingling
	public MyThreadt444(String commandstr) {
		this.commandstr = commandstr;
	}
	@Override
	public String get() {
		int sum = 0;
		for (int i = 0; i < 40; i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			sum += i;
			System.out.println("Mythread444: "+i);
		}
		return String.valueOf(sum+400000);
	}
}

在CompletableFuture接口中除了使用whenComplete還可以使用handle等方法能實現按照任務完成的先後順序獲取任務的結果。

4、幾種多線程併發取結果方式的總結

下圖參考自:https://blog.csdn.net/u011726984/article/details/79320004

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章