【JUC編程】-多線程和CompletableFuture的使用

多線程編程

引言

為什么使用多線程?

  1. 最直接的就是提升程序性能,使用多線程可以充分利用硬件資源,同時執行多個任務,從而提高程序的整體性能。通過并行執行任務,可以將工作負載分布到多個線程上,從而更有效地利用 CPU 資源。
  2. 提高響應性:可以將長時間處理的請求放在后臺另一個線程進行處理,不妨礙主線程的用戶執行其他的請求
  3. 實現并發編程:現在的工作中,多線程是并發編程的一種重要方式。利用好多線程機制可以大大提高系統整體的并發能力以及性能。

創建多線程的方式

從實現上來說,Java提供了三種創建線程的方式,但從原理上來看,其實只有一種方式,我們先從實現上來簡單介紹一下這三種方式

繼承Thread類

直接創建一個ThreadTest的實例,調用它的start()方法就可以創建一個線程了

class ThreadTest extends Thread{@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}

實現Runnable接口

如果只是簡單的實現了Runnable接口,它與線程并沒有任何關系,只是相當于創建了一個線程執行的任務類而已,要想真正的創建線程,還是需要創建一個Thread對象,把RunnableTest實例作為構造方法的入參

1.2 實現Runnable接口
如果只是簡單的實現了Runnable接口,它與線程并沒有任何關系,只是相當于創建了一個線程執行的任務類而已,要想真正的創建線程,還是需要創建一個Thread對象,把RunnableTest實例作為構造方法的入參

class RunnableTest implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}public class CreateThreadTest {public static void main(String[] args) {RunnableTest runnableTest = new RunnableTest();Thread thread = new Thread(runnableTest);thread.start();}
}

實現Callable接口

與Runnable很相似,它相當于也是也個任務的實現類,需要結合線程池的submit()方法才能使用,但與Runnable最本質的區別是,Callable的call()方法可以有返回值

class CallableTest implements Callable<Integer>{@Overridepublic Integer call() throws Exception {return ThreadLocalRandom.current().nextInt();}
}public class CreateThreadTest {public static void main(String[] args) {CallableTest callableTest = new CallableTest();ExecutorService executorService = Executors.newFixedThreadPool(10);Future<Integer> future = executorService.submit(callableTest);}
}
Callable和Runnable的區別

Callable的call方法可以有返回值,可以聲明拋出異常。和 Callable配合的有一個Future類,通過Future可以了解任務執行情況,或者取消任務的執行,還可獲取任務執行的結果,這些功能都是Runnable做不到的,Callable 的功能要比Runnable強大。

@FunctionalInterface
public interface Runnable {// 沒有返回值public abstract void run();
}@FunctionalInterface
public interface Callable<V> {// 有返回值V call() throws Exception;
}

Lambda表達式

這種方式與第二種方式其實是一樣的,只是寫法比較簡潔明了

Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()

線程的實現原理

這三種創建線程的方式,第一種是直接通過繼承Thread進行實現,第二種是通過實現Runnable接口,然后將類作為創建Thread類的入參,其實也是通過實現創建Thread類進行實現。

現在看第三種Callable的方式到底是怎么實現的,他是通過實現Callable接口,然后使用submit進行執行,這里我們通過debug這個方法,最后發現其實也是通過創建的Thread進行實現。

在這里插入圖片描述

**總結:**所以最后我們發現三種方式其實都是創建Thread類進行實現

Future&FutureTask

我們一共有三種創建線程的方式,繼承Thread和實現Runnable接口都是沒有返回值的,所以我們不知道線程的執行狀態,不能獲取執行完成的一個結果。所以這時就需要Callable來解決上面的問題,通過CallableFuture能夠獲得執行的結果。

具體使用

public class CompletableFutureTest {private static ThreadPoolExecutor executor;static {executor = new ThreadPoolExecutor(10, 10, 100, TimeUnit.HOURS, new ArrayBlockingQueue<>(100), new ThreadFactory() {private int count = 0;@Overridepublic Thread newThread(Runnable r) {count++;System.out.printf("CustomerThread- %d :", count);return new Thread(r, "CustomerThread-" + count);}});}static class CallableTest implements Callable<String>{@Overridepublic String call() throws Exception {Thread.sleep(1000);System.out.println("線程開始運行");return "返回值";}}public static void main(String[] args) throws Exception{CallableTest test = new CallableTest();FutureTask<String> futureTask = new FutureTask<>(test);executor.submit(futureTask);System.out.println(futureTask.get());executor.shutdown();}
}

submit方法

在該方法中,我們傳入的是FutureTask類型的,結果把參數轉成了RunnableFuture,任務執行依然是execute()方法

public Future<?> submit(Runnable task) {if (task == null) throw new NullPointerException();RunnableFuture<Void> ftask = newTaskFor(task, null);execute(ftask);return ftask;
}protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {return new FutureTask<T>(callable);
}

這里的Runnable其實是FutureTask的父類

在這里插入圖片描述

當我們使用Callable類的子類作為參數時候,其實也是轉換為RunnableFuture,然后使用excute進行執行。

    public <T> Future<T> submit(Callable<T> task) {if (task == null) throw new NullPointerException();RunnableFuture<T> ftask = newTaskFor(task);execute(ftask);return ftask;}

Future到FutureTask類

Future其實就是定義了一組接口,Future就是對于具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果

在這里插入圖片描述

FutureTask實現了這個接口,同時還實現了Runnalbe接口,這樣FutureTask就相當于是消費者和生產者的橋梁了,消費者可以通過FutureTask存儲任務的執行結果,跟新任務的狀態:未開始、處理中、已完成、已取消等等。而任務的生產者可以拿到FutureTask被轉型為Future接口,可以阻塞式的獲取處理結果,非阻塞式獲取任務處理狀態

**總結:**FutureTask既可以被當做Runnable放入Excutor來執行,也可以被當做Future來獲取Callable的返回結果。

Future

注意事項
  • 當 for 循環批量獲取Future的結果時容易 block,get 方法調用時應使用 timeout 限制
    • 因為我們可能會有耗時的任務,后面的任務只能等前面耗時的任務完成以后才能獲取結果,所以有時會卡住
  • Future 的生命周期不能后退。一旦完成了任務,它就永久停在了“已完成”的狀態,不能從頭再來
局限性

從本質上說,Future表示一個異步計算的結果。它提供了isDone()來檢測計算是否已經完成,并且在計算結束后,可以通過get()方法來獲取計算結果。在異步計算中,Future確實是個非常優秀的接口。但是,它的本身也確實存在著許多限制:

  • 并發執行多任務:Future只提供了get()方法來獲取結果,并且是阻塞的。所以,除了等待你別無他法;
  • 無法對多個任務進行鏈式調用:如果你希望在計算任務完成后執行特定動作,比如發郵件,但Future卻沒有提供這樣的能力;
  • 無法組合多個任務:如果你運行了10個任務,并期望在它們全部執行結束后執行特定動作,那么在Future中這是無能為力的;
  • 沒有異常處理:Future接口中沒有關于異常處理的方法;

而這些局限性CompletionServiceCompletableFuture都解決了。

CompletionService

引言

CompletionService是一個為了解決我們并發執行多個線程的任務的時候,能夠及時獲取已經完成任務的結果而創建的一個抽象的接口類,我們一般是使用的他的實現類ExecutorCompletionService

使用

具體的使用規則還有方法的作用博客鏈接

使用場景

  1. 當需要批量提交異步任務的時候建議使用CompletionService。CompletionService將線程池Executor和阻塞隊列BlockingQueue的功能融合在了一起,能夠讓批量異步任務的管理更簡單。
  2. CompletionService能夠讓異步任務的執行結果有序化。先執行完的先進入阻塞隊列,利用這個特性,你可以輕松實現后續處理的有序性,避免無謂的等待,同時還可以快速實現諸如Forking Cluster這樣的需求。
  3. 線程池隔離。CompletionService支持自己創建線程池,這種隔離性能避免幾個特別耗時的任務拖垮整個應用的風險。

CompletableFuture

引言

我們使用CompletionService能夠解決多個線程并發執行時,獲取執行結果的返回值時的阻塞問題。但是假如我們并發執行的多線程任務需要遵循一定的規則,或者執行的順序時候,CompletionService就不能滿足我們的需求了。

所以CompletableFuture其實是對Future進行擴展,彌補了Future的局限性,同時CompletableFuture實現了對任務編排的能力

在以往,雖然通過**CountDownLatch**等工具類也可以實現任務的編排,但需要復雜的邏輯處理,不僅耗費精力且難以維護。

在這里插入圖片描述

更加詳細介紹博客

繼承結構

CompletionStage接口定義了任務編排的方法,執行某一階段,可以向下執行后續階段。異步執行的,默認線程池是ForkJoinPool.commonPool(),但為了業務之間互不影響,且便于定位問題,強烈推薦使用自定義線程池

在這里插入圖片描述

任務的異步回調

在這里插入圖片描述

多個任務組合處理

在這里插入圖片描述

注意點

Future需要獲取返回值,才能獲取異常信息
ExecutorService executorService = new ThreadPoolExecutor(5, 10, 5L,TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {int a = 0;int b = 666;int c = b / a;return true;},executorService).thenAccept(System.out::println);//如果不加 get()方法這一行,看不到異常信息//future.get();

Future需要獲取返回值,才能獲取到異常信息。如果不加 get()/join()方法,看不到異常信息。小伙伴們使用的時候,注意一下哈,考慮是否加try…catch…或者使用exceptionally方法。

CompletableFuture的get()方法是阻塞的。

CompletableFuture的get()方法是阻塞的,如果使用它來獲取異步調用的返回值,需要添加超時時間~

csharp復制代碼//反例CompletableFuture.get();
//正例
CompletableFuture.get(5, TimeUnit.SECONDS);
默認線程池的注意點

CompletableFuture代碼中又使用了默認的線程池,處理的線程個數是電腦CPU核數-1。在大量請求過來的時候,處理邏輯復雜的話,響應會很慢。一般建議使用自定義線程池,優化線程池配置參數。

自定義線程池時,注意飽和策略

CompletableFuture的get()方法是阻塞的,我們一般建議使用future.get(3, TimeUnit.SECONDS)。并且一般建議使用自定義線程池。

但是如果線程池拒絕策略是DiscardPolicy或者DiscardOldestPolicy,當線程池飽和時,會直接丟棄任務,不會拋棄異常。因此建議,CompletableFuture線程池策略最好使用AbortPolicy,然后耗時的異步線程,做好線程池隔離哈。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/17368.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/17368.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/17368.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第八大奇跡

目錄 題目描述 輸入描述 輸出描述 輸入輸出樣例 示例 輸入 輸出 運行限制 原題鏈接 代碼思路 題目描述 在一條 R 河流域&#xff0c;繁衍著一個古老的名族 Z。他們世代沿河而居&#xff0c;也在河邊發展出了璀璨的文明。 Z 族在 R 河沿岸修建了很多建筑&#xff0c…

java如何向數組中插入元素

java的數組是不可改變的&#xff0c;因此如果要向數組中插入新的元素&#xff0c;需要新建一個數組&#xff0c;新的數組元素個數減去老數組元素個數的差大于等于要插入新的元素數量。 假如說要插入一個數組元素&#xff0c;需要把新元素插入到中間&#xff0c;把新的數組分為…

Vue組件通訊?組件中通過 provide 來提供變量,然后在?組件中通過 inject 來注?變量例子

在Vue中&#xff0c;provide 和 inject 主要用于依賴注入&#xff0c;允許祖先組件向其所有子孫組件提供一個依賴&#xff0c;而不論組件層次有多深。這在開發高階插件/組件庫時特別有用。 以下是一個簡單的例子&#xff0c;演示了如何在父組件中使用 provide 提供變量&#x…

軟件測試面試題(八)

一&#xff1a;TestDirector有哪些功能&#xff0c;如何對軟件測試過程進行管理&#xff1f; 需求管理 定義測試范圍 定義需求樹 描述需求樹的功能點 測試計劃 定義測試目標和測試策略 分解應用程序&#xff0c;建立測試計劃樹 確定每個功能點的測試方法 將每個功能點連接…

Ps 濾鏡:消失點

Ps菜單&#xff1a;濾鏡/消失點 Filter/Vanishing Point 快捷鍵&#xff1a;Ctrl Alt V 兩條平行的鐵軌或兩排樹木連線相交于很遠很遠的某一點&#xff0c;這點在透視圖中叫做“消失點”&#xff0c;也稱為“滅點”。 消失點 Vanishing Point濾鏡主要用于在圖像中處理具有透視…

C++入門3——類與對象(2)

1.類的6個默認成員函數 如果一個類中什么成員都沒有&#xff0c;簡稱為空類。可是空類中真的什么都沒有嗎&#xff1f; 其實并不是的&#xff0c;任何類在什么都不寫時&#xff0c;編譯器會自動生成以下6個默認成員函數。 默認成員函數&#xff1a;用戶沒有顯式實現&#xf…

libmodbus開發庫介紹

目錄 功能概要源碼獲取源碼內容結構源碼與移植 功能概要 libmodbus是一個免費的跨平臺支持RTU和TCP的Modbus庫&#xff0c;遵循LGPL V2.1協議。libmodbus支持Linux、Mac Os X、FreeBSD、QNX和Windows等操作系統。libmodbus可以向符合Modbus協議的設備發送和接收數據&#xff0…

vector的reverse和resize區別

一 代碼 #include "stdafx.h" #include <iostream> #include <vector> using namespace std;class TEST{ public:TEST(){std::cout << "construct t" << std::endl;} };int main() {std::cout << "hello,world" …

《Python偵探手冊:用正則表達式破譯文本密碼》

在這個信息爆炸的時代&#xff0c;每個人都需要一本偵探手冊。阿佑今天將帶你深入Python的正則表達式世界&#xff0c;教你如何像偵探一樣&#xff0c;用代碼破解文本中的每一個謎題。從基礎的字符匹配到復雜的數據清洗&#xff0c;每一個技巧都足以讓你在文本處理的領域中成為…

【一站式學會Kotlin】第十三節:kotlin語言中的解構

作者介紹: 百度資深Android工程師T6,在百度任職7年半。 目前:成立趙小灰代碼工作室,歡迎大家找我交流Android、微信小程序、鴻蒙項目。= 一:通俗易懂的人工智能教程:https://www.captainbed.cn/nefu/ 點一下,打開新世界的大門。 二:【一站式學會Kotlin】免費領取:作者…

SQLSyntaxErrorException: FUNCTION dbname.to_timestamp does not exist

由于MySQL數據庫高版本&#xff08;如8.x&#xff09;中有to_timestamp(&#xff09;函數&#xff0c;低版本中&#xff08;如5.7.x&#xff09;沒有這個函數&#xff0c;服務運行報錯。 自己創建函數實現功能&#xff0c;創建語句如下&#xff1b; DELIMITER // CREATE FUN…

如何使用ChatGPT撰寫短視頻爆款文案

在這個快速發展的數字時代&#xff0c;短視頻已經成為最受歡迎的娛樂和信息獲取方式之一。對于內容創作者來說&#xff0c;如何制作出爆款短視頻&#xff0c;吸引更多觀眾的注意力&#xff0c;是他們面臨的一大挑戰。文案&#xff0c;作為視頻內容的靈魂&#xff0c;起著至關重…

ESP32 - Micropython ESP-IDF 雙線教程 中斷和定時器 (1)

ESP32 - Micropython ESP-IDF 雙線教程 中斷和定時器 ESP32中斷ESP32定時器歸納ESP32 - Micropython 定時器示例代碼代碼介紹 ESP32 - IDF 定時器示例代碼代碼解釋ESP32-IDF定時器使用介紹 ESP32中的中斷和定時器是兩種重要的硬件特性&#xff0c;它們在嵌入式系統開發中扮演著…

系統思考—戰略沙盤推演咨詢服務

今日與JSTO團隊一起學習了《戰略沙盤推演咨詢服務》。通過沙盤體驗&#xff0c;我深刻感受到組織與戰略就像一張皮的正反兩面。在轉型過程中&#xff0c;即使戰略非常明確&#xff0c;團隊成員由于恐懼和顧慮&#xff0c;往往不愿意挑戰新的業務&#xff0c;從而難以實現戰略目…

VasDolly圖形工具-Android多渠道打包福利

簡介 基于騰訊VasDolly最新版本3.0.6的圖形界面衍生版本&#xff0c;旨在更好的幫助開發者構建多渠道包 使用 下載并解壓工具包&#xff0c;找到Startup腳本并雙擊啟動圖形界面&#xff08;注意&#xff1a;本地需安裝java環境&#xff09; 渠道格式說明 txt文件&#xff…

音頻鏈接抓取技術在Lua中的實現

前言 隨著數字音樂的普及&#xff0c;越來越多的用戶選擇在線音樂平臺來享受音樂。網易云音樂作為國內領先的音樂服務平臺&#xff0c;不僅提供了豐富的音樂資源&#xff0c;還擁有獨特的社交屬性&#xff0c;吸引了大量的用戶。在眾多的音樂服務中&#xff0c;音頻鏈接的抓取…

Qt | QTabBar 類(選項卡欄)

01、上節回顧 Qt | QStackedLayout 類(分組布局或棧布局)、QStackedWidget02、簡介 1、QTabBar類直接繼承自 QWidget。該類提供了一個選項卡欄,該類僅提供了一個選項卡, 并沒有為每個選項卡提供相應的頁面,因此要使選項卡欄實際可用,需要自行為每個選項卡設置需要顯示的頁…

【面試題】JavaScript基礎高頻面試(上)

1、簡述JavaScript中map和foreach的區別&#xff1f; map和forEach都是JavaScript數組的迭代方法&#xff0c;但它們之間存在一些關鍵區別。 1. 返回值&#xff1a;map方法會返回一個新的數組&#xff0c;這個新數組是由原數組通過某個函數處理后的結果組成的。而forEach方法…

Ubuntu18.04 重裝/升級 eigen 教程

目錄 一、Eigen 1.1 ubuntu 查看 eigen 版本 1.2 卸載 老版本 eigen 二、安裝 eigen 3.4.0 2.1 配置安裝 2.2 查看版本 一、Eigen 1.1 ubuntu 查看 eigen 版本 $ dpkg -l | grep eigen1.2 卸載 老版本 eigen sudo updatedb locate eigen3會獲得一堆輸出&#xff0c;其…

springboot整合Kafka的快速使用教程

目錄 一、引入Kafka的依賴 二、配置Kafka 三、創建主題 1、自動創建(不推薦) 2、手動動創建 四、生產者代碼 五、消費者代碼 六、常用的KafKa的命令 Kafka是一個高性能、分布式的消息發布-訂閱系統&#xff0c;被廣泛應用于大數據處理、實時日志分析等場景。Spring B…