關於Future的想法

前言: 最近在看RPC框架的書,書裏提到了Future的使用,發現我對這個並不是很瞭解,所以自學一下Future。

1 Future是什麼:

在併發編程中,我們經常用到非阻塞的模型,在之前的多線程的三種實現中,不管是繼承thread類還是實現runnable接口,都無法保證獲取到之前的執行結果。通過實現Callback接口,並用Future可以來接收多線程的執行結果。Future表示一個可能還沒有完成的異步任務的結果,針對這個結果可以添加Callback以便在任務執行成功或失敗後作出相應的操作。

尋找資料的過程中,發現了一個博主寫的有趣的案例,看完後我覺得我有一個可以不用Future就能解決的方案,測試了,和他的時間一樣,寫出來大家看看,我想看完這個之後大家會更容易理解Future的

該博主的問題場景是:要做菜,得先去買廚具,買菜,這兩樣都齊全了之後纔可以開始做菜。
一般的操作邏輯可能是:先去買菜或者先去買廚具,是串行邏輯,導致效率低下,該博主就想用Future去優化這個操作邏輯,是他們變成並行的操作。

package com.kinglong.springboot.service.impl;

/**
 * 原問題:串行邏輯導致效率低下
 *
 * @author haojinlong
 * @date 2019/7/2815:10
 */
public class CommonCook01 {

	public static void main(String[] args) throws InterruptedException {
		long startTime = System.currentTimeMillis();
		// 第一步 網購廚具
		OnlineShopping thread = new OnlineShopping();
		thread.start();
		thread.join();  // 保證廚具送到
		// 第二步 去超市購買食材
		Thread.sleep(2000);  // 模擬購買食材時間
		Shicai shicai = new Shicai();
		System.out.println("第二步:食材到位");
		// 第三步 用廚具烹飪食材
		System.out.println("第三步:開始展現廚藝");
		cook(thread.chuju, shicai);

		System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
	}

	// 網購廚具線程
	static class OnlineShopping extends Thread {

		private Chuju chuju;

		@Override
		public void run() {
			System.out.println("第一步:下單");
			System.out.println("第一步:等待送貨");
			try {
				Thread.sleep(5000);  // 模擬送貨時間
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("第一步:快遞送到");
			chuju = new Chuju();
		}

	}

	//  用廚具烹飪食材
	static void cook(Chuju chuju, Shicai shicai) {
	}

	// 廚具類
	static class Chuju {
	}

	// 食材類
	static class Shicai {
	}

}

原邏輯耗時:

第一步:下單
第一步:等待送貨
第一步:快遞送到
第二步:食材到位
第三步:開始展現廚藝
總共用時7005ms

該博主優化後的

package com.kinglong.springboot.service.impl;

import java.util.concurrent.*;

/**
 * 該博主的方案:使用FutureTask實現
 *
 * @author haojinlong
 * @date 2019/7/2815:10
 */
public class CommonCook02 {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		long startTime = System.currentTimeMillis();
		// 第一步 網購廚具
		Callable<Chuju> onlineShopping = new Callable<Chuju>() {

			@Override
			public Chuju call() throws Exception {
				System.out.println("第一步:下單");
				System.out.println("第一步:等待送貨");
				Thread.sleep(5000);  // 模擬送貨時間
				System.out.println("第一步:快遞送到");
				return new Chuju();
			}
		};

		FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
		new Thread(task).start();
		// 第二步 去超市購買食材
		Thread.sleep(2000);  // 模擬購買食材時間
		Shicai shicai = new Shicai();
		System.out.println("第二步:食材到位");
		// 第三步 用廚具烹飪食材
		if (!task.isDone()) {  // 聯繫快遞員,詢問是否到貨
			System.out.println("第三步:廚具還沒到,心情好就等着(心情不好就調用cancel方法取消訂單)");
		}
		Chuju chuju = task.get();
		System.out.println("第三步:廚具到位,開始展現廚藝");
		cook(chuju, shicai);

		System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
	}

	//  用廚具烹飪食材
	static void cook(Chuju chuju, Shicai shicai) {}

	// 廚具類
	static class Chuju {}

	// 食材類
	static class Shicai {}

}

該博主優化後的耗時

第一步:下單
第一步:等待送貨
第二步:食材到位
第三步:廚具還沒到,心情好就等着(心情不好就調用cancel方法取消訂單)
第一步:快遞送到
第三步:廚具到位,開始展現廚藝
總共用時5105ms

以下是我的實現

package com.kinglong.springboot.service.impl;

import java.util.concurrent.*;

/**
 * 我的方案:使用CountDownLatch實現
 *
 * @author haojinlong
 * @date 2019/7/2815:10
 */
public class CommonCook03 {

	private static Chuju chuju = null;
	private static Shicai shicai = null;

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		long startTime = System.currentTimeMillis();
		CountDownLatch countDownLatch = new CountDownLatch(2);
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
		// 第一步 網購廚具
		OnlineShopping(fixedThreadPool,countDownLatch);
		// 第二步 去超市購買食材
		BuyShiCai(fixedThreadPool, countDownLatch);
		countDownLatch.await();
		cook(chuju, shicai);
		System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
	}

	static void OnlineShopping(ExecutorService fixedThreadPool,CountDownLatch countDownLatch){
		fixedThreadPool.execute(new Runnable() {
			public void run() {
				System.out.println("第一步:下單");
				System.out.println("第一步:等待送貨");
				try {
					Thread.sleep(5000);  // 模擬送貨時間
				} catch (InterruptedException e) {
					countDownLatch.countDown();
					e.printStackTrace();
				}
				System.out.println("第一步:快遞送到");
				chuju = new Chuju();
				System.out.println("第三步:廚具到位,開始展現廚藝");
				countDownLatch.countDown();
			}
		});

	}

	static void BuyShiCai(ExecutorService fixedThreadPool,CountDownLatch countDownLatch){
		fixedThreadPool.execute(new Runnable() {
			public void run() {
				try {
					Thread.sleep(2000);  // 模擬購買食材時間
				} catch (InterruptedException e) {
					countDownLatch.countDown();
					e.printStackTrace();
				}
				shicai = new Shicai();
				System.out.println("第二步:食材到位");
				countDownLatch.countDown();
			}
		});

	}

	//  用廚具烹飪食材
	static void cook(Chuju chuju, Shicai shicai) {}

	// 廚具類
	static class Chuju {}

	// 食材類
	static class Shicai {}

}

我的實現耗時

第一步:下單
第一步:等待送貨
第二步:食材到位
第一步:快遞送到
第三步:廚具到位,開始展現廚藝
總共用時5033ms

總結:
從程序的執行時間可以看出來,我的方案和答主的方案耗時其實相差不大,雙方的思路一樣,都是將串行變並行,不同的是它採用的是Future而我採用的是countDownLatch,因爲我平時使用countDownLatch場景較多,比較熟悉。
說到這,想必大家也差不多明白了Future的使用場景,也就是用在異步執行某個請求,並且能夠返回執行結果。

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