1. 程式人生 > >Java併發的四種風味:Thread、Executor、ForkJoin和Actor

Java併發的四種風味:Thread、Executor、ForkJoin和Actor

這篇文章討論了Java應用中並行處理的多種方法。從自己管理Java執行緒,到各種更好幾的解決方法,Executor服務、ForkJoin 框架以及計算中的Actor模型。

Java併發程式設計的4種風格:Threads,Executors,ForkJoin和Actors

我們生活在一個事情並行發生的世界。自然地,我們編寫的程式也反映了這個特點,它們可以併發的執行。當然除了Python程式碼(譯者注:連結裡面講述了Python的全域性直譯器鎖,解釋了原因),不過你仍然可以使用Jython在JVM上執行你的程式,來利用多處理器電腦的強大能力。

然而,併發程式的複雜程度遠遠超出了人類大腦的處理能力。相比較而言,我們簡直弱爆了:我們生來就不是為了思考多執行緒程式、評估併發訪問有限資源以及預測哪裡會發生錯誤或者瓶頸。

面對這些困難,人類已經總結了不少併發計算的解決方案和模型。這些模型強調問題的不同部分,當我們實現平行計算時,可以根據問題做出不同的選擇。

在這篇文章中,我將會用對同一個問題,用不同的程式碼來實現併發的解決方案;然後討論這些方案有哪些好的地方,有哪些缺陷,可能會有什麼樣的陷阱在等著你。

我們將介紹下面幾種併發處理和非同步程式碼的方式:

• 裸執行緒

• Executors和Services

• ForkJoin框架和並行流

• Actor模型

為了更加有趣一些,我沒有僅僅通過一些程式碼來說明這些方法,而是使用了一個共同的任務,因此每一節中的程式碼差不多都是等價的。另外,這些程式碼僅僅是展示用的,初始化的程式碼並沒有寫出來,並且它們也不是產品級的軟體示例。

對了,最後一件事:在文章最後,有一個小調查,關於你或者你的組織正在使用哪種併發模式。為了你的工程師同胞們,請填一下調查!

任務

任務:實現一個方法,它接收一條訊息和一組字串作為引數,這些字串與某個搜尋引擎的查詢頁面對應。對每個字串,這個方法發出一個http請求來查詢訊息,並返回第一條可用的結果,越快越好。

如果有錯誤發生,丟擲一個異常或者返回空都是可以的。我只是嘗試避免為了等待結果而出現無限迴圈。

簡單說明:這次我不會真正深入到多執行緒如何通訊的細節,或者深入到Java記憶體模型。如果你迫切地想了解這些,你可以看我前面的文章利用JCStress測試併發

那麼,讓我們從最直接、最核心的方式來在JVM上實現併發:手動管理裸執行緒。

方法1:使用“原汁原味”的裸執行緒

解放你的程式碼,迴歸自然,使用裸執行緒!執行緒是併發最基本的單元。Java執行緒本質上被對映到作業系統執行緒,並且每個執行緒物件對應著一個計算機底層執行緒。

自然地,JVM管理著執行緒的生存期,而且只要你不需要執行緒間通訊,你也不需要關注執行緒排程。

每個執行緒有自己的棧空間,它佔用了JVM程序空間的指定一部分。

執行緒的介面相當簡明,你只需要提供一個Runnable,呼叫.start()開始計算。沒有現成的API來結束執行緒,你需要自己來實現,通過類似boolean型別的標記來通訊。

在下面的例子中,我們對每個被查詢的搜尋引擎,建立了一個執行緒。查詢的結果被設定到AtomicReference,它不需要鎖或者其他機制來保證只出現一次寫操作。開始吧!

1 2 3 4 5 6 7 8 9 10 11 private static String getFirstResult(String question, List<String> engines) { AtomicReference<String> result = new AtomicReference<>(); for(String base: engines) { String url = base + question; new Thread(() -> { result.compareAndSet(null, WS.url(url).get()); }).start(); } while(result.get() == null); // wait for some result to appear return result.get(); }

使用裸執行緒的主要優點是,你很接近併發計算的作業系統/硬體模型,並且這個模型非常簡單。多個執行緒執行,通過共享記憶體通訊,就是這樣。

自己管理執行緒的最大劣勢是,你很容易過分的關注執行緒的數量。執行緒是很昂貴的物件,建立它們需要耗費大量的記憶體和時間。這是一個矛盾,執行緒太少,你不能獲得良好的併發性;執行緒太多,將很可能導致記憶體問題,排程也變得更復雜。

然而,如果你需要一個快速和簡單的解決方案,你絕對可以使用這個方法,不要猶豫。

方法2:認真對待Executor和CompletionService

另一個選擇是使用API來管理一組執行緒。幸運的是,JVM為我們提供了這樣的功能,就是Executor介面。Executor介面的定義非常簡單:

1 2 3 4 5 public interface Executor { void execute(Runnable command); }

它隱藏瞭如何處理Runnable的細節。它僅僅說,“開發者!你只是一袋肉,給我任務,我會處理它!”

更酷的是,Executors類提供了一組方法,能夠建立擁有完善配置的執行緒池和executor。我們將使用newFixedThreadPool(),它建立預定義數量的執行緒,並不允許執行緒數量超過這個預定義值。這意味著,如果所有的執行緒都被使用的話,提交的命令將會被放到一個佇列中等待;當然這是由executor來管理的。

在它的上層,有ExecutorService管理executor的生命週期,以及CompletionService會抽象掉更多細節,作為已完成任務的佇列。得益於此,我們不必擔心只會得到第一個結果。

下面service.take()的一次呼叫將會只返回一個結果。

1 2 3 4 5 6 7 8 9 10 11 12 13

相關推薦

Java併發風味ThreadExecutorForkJoinActor

這篇文章討論了Java應用中並行處理的多種方法。從自己管理Java執行緒,到各種更好幾的解決方法,Executor服務、ForkJoin 框架以及計算中的Actor模型。 Java併發程式設計的4種風格:Threads,Executors,ForkJoin和Actors 我們生

java基礎-排序選擇/冒泡/直接插入/shell

import java.util.Arrays; /** * @Created with IntelliJ IDEA * @Description: 四種排序 * @Package: PACKAGE_NAME * @User: FLy * @Date: 2018/11/21

Java併發程式設計Java執行緒池的使用,以及自定義執行緒工廠

目錄 引言 四種執行緒池 newCachedThreadPool:可快取的執行緒池 newFixedThreadPool:定長執行緒池 newSingleThreadExecutor:單執行緒執行緒池 newScheduledThreadPool:支援定時的定

SQL的語言DDLDMLDCLTCL

edi xpl bottom cts 創建 實現 pad lang create 1. DDL(Data?Definition Language) 數據庫定義

java修飾符(privatedefaultprotectedpublic)的訪問權限

ble ted span java 修飾符 20px col family style 權限如下: no. 範圍 private default protected public 1 同一包下的同一個類 √ √ √ √ 2 同一包下的不同類 × √ √ √

Java併發volatile的實現原理 Java併發(一)Java記憶體模型乾貨總結

synchronized是一個重量級的鎖,volatile通常被比喻成輕量級的synchronized volatile是一個變數修飾符,只能用來修飾變數。 volatile寫:當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體。 volatile讀:當讀一

java註解(元註解@Retention @Target @Document @Inherited)

java中元註解有四個: @Retention @Target @Document @Inherited;    @Retention:註解的保留位置                @Retention(RetentionPolicy.SOURCE)   //註解僅存在於

JavaWeb()--保持Http狀態的方式URL重寫隱藏域Sessioncookie

一、前言: HTTP是無狀態的。因此在預設狀態下,伺服器是不知道一個Http請求是否是來自第一次訪問的專案。 因而有如下四種方法可以保持http的狀態 URL重寫 隱藏域 cookie httpsession物件 二、cookie 2-1:不足之處

JAVA引用型別的作用——強引用軟引用弱引用虛引用

java有四種引用型別,分別是強引用、軟引用、弱引用、虛引用。 背景:我們希望有這樣一種場景像快取一樣, 即:在記憶體還足夠時,希望能夠保留這些物件,當記憶體不夠時,則刪除這些物件(當然是由垃圾回收完成)。 四種引用的強度:強引用(Strong Reference)>軟引用(Sof

降維的方法PCALDALLELaplacian Eigenmaps

知識點:降維的四種方法,PCA、LDA、LLE、Laplacian Eigenmaps 注意區分LDA:  資訊檢索中也有LDA(Latent Dirichlet allocation),主題模型,,表示文件的生成過程:先根據超參選擇主題,在根據主題的分佈取樣得到單詞,重

new Thread的弊端 以及JAVA執行緒池

1、new Thread的弊端 執行一個非同步任務你還只是如下new Thread嗎?new Thread(new Runnable() { @Override public void run() { // TODO Auto-gene

RxJava的SubjectsPublishSubjectReplaySubjectBehaviorSubjectAsyncSubject的理解

Subject:它既是Observable,又是observer。也就是既可以傳送事件,也可以接收事件。 下面是四個子類PublishSubject、ReplaySubject、BehaviorSubject、AsyncSubject的區別: PublishSubject<Integer>

Java引用(強虛)

1. 強引用(StrongReference)  強引用是使用最普遍的引用。如果一個物件具有強引用,那垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足的問題。 2. 軟引用(SoftR

Java併發程式設計之三執行緒掛起恢復與終止的正確方法

出處:http://blog.csdn.NET/ns_code/article/details/17095733 掛起和恢復執行緒 Thread 的API中包含兩個被淘汰的方法,它們用於臨時掛起和重啟某個執行緒,這些方法已經被淘汰,因為它們是不安全的,不穩定的。如果

Java並發編程Thread類的使用

可能 學習 結束 而是 先來 lock 停止 常用 ren 一.線程的狀態   在正式學習Thread類中的具體方法之前,我們先來了解一下線程有哪些狀態,這個將會有助於後面對Thread類中的方法的理解。   線程從創建到最終的消亡,要經歷若幹個狀態。一般來說,線程包括以下

java訪問修飾符

pub oid 默認 成員變量 修飾 對象 fault 其中 () Java中的四種訪問修飾符:public、protected、default(無修飾符,默認)、private。 四種修飾符可修飾的成分(類、方法、成員變量) public protect

Java引用的區分

isn 賦值 public medium 是否 comm 回調 ant container 強引用(StrongReference) 強引用就是指在程序代碼之中普遍存在的,比如下面這段代碼中的object和str都是強引用: 1 2 Object obje

清除浮動的方法額外標簽法,overflowhidden,單偽元素法雙偽元素法

new ont nbsp ola 圖片 分享 col ons pac 當給浮動的元素增加了一個父級元素,但是又不方便給高度的情況下 (父盒子給高度也是一個解決方法,但是大多數情況下,因為盒子的內容會經常改變,父盒子高度固定,需要每次去調整) 此時可以使用下面的四種方法來清

【轉載】HTTP 緩存的風味與緩存策略

href 校驗 成本 字段值 包括 避免 valid 技術 target 原文地址:https://segmentfault.com/a/1190000006689795 HTTP Cache 通過網絡獲取內容既緩慢,成本又高:大的響應需要在客戶端和服務器之間進行多次往返通

Java多線程 5Thread中的實例方法

守護 nds 屬性 exc pre 在線 結束 正在 ini Thread類中的方法調用方式:快速到底 學習 Thread 類中的方法是學習多線程的第一步。在學習多線程之前特別提出一點,調用 Thread 中的方法的時候,在線程類中(千萬別忘記了這個前提條件),有兩種方式