早期的系統是同步的,容易理解,我們來看個例子
同步編程
當用戶創建一筆電商交易訂單時,要經歷的業務邏輯流程還是很長的,每一步都要耗費一定的時間,那么整體的RT就會比較長。
于是,聰明的人們開始思考能不能將一些非核心業務從主流程中剝離出來,于是有了異步編程
雛形。
異步編程是讓程序并發運行的一種手段。它允許多個事件同時發生,當程序調用需要長時間運行的方法時,它不會阻塞當前的執行流程,程序可以繼續運行。
核心思路:采用多線程優化性能,將串行操作變成并行操作。異步模式設計的程序可以顯著減少線程等待,從而在高吞吐量場景中,極大提升系統的整體性能,顯著降低時延。
接下來,我們來講下異步有哪些編程實現方式
一、線程 Thread
直接繼承 Thread類
是創建異步線程最簡單的方式。
首先,創建Thread子類,普通類或匿名內部類方式;然后創建子類實例;最后通過start()方法啟動線程。
public class AsyncThread extends Thread{
@Override
public void run() {
System.out.println("當前線程名稱:" + this.getName() + ", 執行線程名稱:" + Thread.currentThread().getName() + "-hello");
}
}
public static void main(String[] args) {
// 模擬業務流程
// .......
// 創建異步線程
AsyncThread asyncThread = new AsyncThread();
// 啟動異步線程
asyncThread.start();
}
當然如果每次都創建一個 Thread線程
,頻繁的創建、銷毀,浪費系統資源。我們可以采用線程池
@Bean(name = "executorService")
public ExecutorService downloadExecutorService() {
return new ThreadPoolExecutor(20, 40, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000),
new ThreadFactoryBuilder().setNameFormat("defaultExecutorService-%d").build(),
(r, executor) -> log.error("defaultExecutor pool is full! "));
}
將業務邏輯封裝到 Runnable
或 Callable
中,交由 線程池
來執行
二、Future
上述方式雖然達到了多線程并行處理,但有些業務不僅僅要執行過程,還要獲取執行結果。
Java 從1.5版本開始,提供了 Callable
和 Future
,可以在任務執行完畢之后得到任務執行結果。
當然也提供了其他功能,如:取消任務、查詢任務是否完成等
Future類位于java.util.concurrent包下,接口定義:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
方法描述:
- cancel():取消任務,如果取消任務成功返回true,如果取消任務失敗則返回false
- isCancelled():表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true
- isDone():表示任務是否已經完成,如果完成,返回true
- get():獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回
- get(long timeout, TimeUnit unit):用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null
代碼示例:
public class CallableAndFuture {
public static ExecutorService executorService = new ThreadPoolExecutor(4, 40,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(1024), new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build(), new ThreadPoolExecutor.AbortPolicy());
static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "異步處理,Callable 返回結果";
}
}
public static void main(String[] args) {
Future future = executorService.submit(new MyCallable());
try {
System.out.println(future.get());
} catch (Exception e) {
// nodo
} finally {
executorService.shutdown();
}
}
}
Future 表示一個可能還沒有完成的異步任務的結果,通過 get
方法獲取執行結果,該方法會阻塞直到任務返回結果。
三、FutureTask
FutureTask
實現了 RunnableFuture
接口,則 RunnableFuture
接口繼承了 Runnable
接口和 Future
接口,所以可以將 FutureTask
對象作為任務提交給 ThreadPoolExecutor
去執行,也可以直接被 Thread
執行;又因為實現了 Future
接口,所以也能用來獲得任務的執行結果。
FutureTask 構造函數:
public FutureTask(Callable callable)
public FutureTask(Runnable runnable, V result)
FutureTask 常用來封裝 Callable
和 Runnable
,可以作為一個任務提交到線程池中執行。除了作為一個獨立的類之外,也提供了一些功能性函數供我們創建自定義 task 類使用。
FutureTask 線程安全由CAS來保證。
ExecutorService executor = Executors.newCachedThreadPool();
// FutureTask包裝callbale任務,再交給線程池執行
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
System.out.println("子線程開始計算:");
Integer sum = 0;
for (int i = 1; i <= 100; i++)
sum += i;
return sum;
});
// 線程池執行任務, 運行結果在 futureTask 對象里面
executor.submit(futureTask);
try {
System.out.println("task運行結果計算的總和為:" + futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
executor.shutdown();
Callable 和 Future 的區別:Callable 用于產生結果,Future 用于獲取結果
如果是對多個任務多次自由串行、或并行組合,涉及多個線程之間同步阻塞獲取結果,Future 代碼實現會比較繁瑣,需要我們手動處理各個交叉點,很容易出錯。
四、異步框架 CompletableFuture
Future 類通過 get()
方法阻塞等待獲取異步執行的運行結果,性能比較差。
JDK1.8 中,Java 提供了 CompletableFuture
類,它是基于異步函數式編程。相對阻塞式等待返回結果,CompletableFuture
可以通過回調的方式來處理計算結果,實現了異步非阻塞,性能更優。
優點 :
- 異步任務結束時,會自動回調某個對象的方法
- 異步任務出錯時,會自動回調某個對象的方法
- 主線程設置好回調后,不再關心異步任務的執行
泡茶示例:
(內容摘自:極客時間的《Java 并發編程實戰》)
//任務1:洗水壺->燒開水
CompletableFuture<Void> f1 =
CompletableFuture.runAsync(() -> {
System.out.println("T1:洗水壺...");
sleep(1, TimeUnit.SECONDS);
System.out.println("T1:燒開水...");
sleep(15, TimeUnit.SECONDS);
});
//任務2:洗茶壺->洗茶杯->拿茶葉
CompletableFuture f2 =
CompletableFuture.supplyAsync(() -> {
System.out.println("T2:洗茶壺...");
sleep(1, TimeUnit.SECONDS);
System.out.println("T2:洗茶杯...");
sleep(2, TimeUnit.SECONDS);
System.out.println("T2:拿茶葉...");
sleep(1, TimeUnit.SECONDS);
return "龍井";
});
//任務3:任務1和任務2完成后執行:泡茶
CompletableFuture f3 =
f1.thenCombine(f2, (__, tf) -> {
System.out.println("T1:拿到茶葉:" + tf);
System.out.println("T1:泡茶...");
return "上茶:" + tf;
});
//等待任務3執行結果
System.out.println(f3.join());
}
CompletableFuture 提供了非常豐富的API,大約有50種處理串行,并行,組合以及處理錯誤的方法。
-
編程
+關注
關注
88文章
3595瀏覽量
93600 -
程序
+關注
關注
116文章
3777瀏覽量
80853 -
異步
+關注
關注
0文章
62瀏覽量
18034
發布評論請先 登錄
相關推薦
評論