1. 程式人生 > >Java併發程式設計系列-(9) JDK 8/9/10中的併發

Java併發程式設計系列-(9) JDK 8/9/10中的併發

9.1 CompletableFuture

CompletableFuture是JDK 8中引入的工具類,實現了Future介面,對以往的FutureTask的功能進行了增強。

手動設定完成狀態

CompletableFuture和Future一樣,可以作為函式呼叫的契約,當向CompletableFuture請求資料時,如果資料還沒有準備好,請求執行緒就會等待。但是,我們可以手動設定CompletableFuture的完成狀態。

下面的例子中,建立了CompletableFuture物件例項進行計算,同時另外一個執行緒進行等待,接著,模擬等待一段時間之後,設定完成狀態為完成,此時等待執行緒繼續執行。

public class CompletableFutureTest {

    public static class waitThread implements Runnable {
        CompletableFuture<Integer> resultCompletableFuture = null;

        public waitThread(CompletableFuture<Integer> resultCompletableFuture) {
            super();
            this.resultCompletableFuture = resultCompletableFuture;
        }
        
        @Override
        public void run() {
            int myResult = 0;
            try {
                System.out.println("Waiting for the result...");
                myResult = resultCompletableFuture.get();
                System.out.println("Result got, it's " + myResult);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        final CompletableFuture<Integer> future = new CompletableFuture<Integer>();
        new Thread(new waitThread(future)).start();
        // 模擬等待過程
        Thread.sleep(2000);
        // 設定完成的結果
        future.complete(666);
    }
}

非同步執行任務

CompletableFuture提供了很方便的非同步執行介面,

其中supplyAsync方法用於需要返回值的場景;runAsync方法用於沒有返回值的場景。注意到,這兩個方法中都可以接受Executor引數,可以方便的讓任務在指定的執行緒池中執行。

流式呼叫

CompletableFuture提供了類似於JDK 8中list的流式操作,下面例子中,首先利用supplyAsync()執行一個非同步任務,接著使用流式操作對任務結果進行處理。

注意到在最後面,呼叫了get方法,用於獲取結果,否則由於CompletableFuture非同步執行,main函式不會等待計算完成,直接退出,隨著主執行緒的結束,所有的Daemon執行緒也會退出,從而導致計算方法無法正常完成。

異常處理

CompletableFuture在執行中遇到異常時,同樣的可以利用函數語言程式設計的方法來處理異常,CompletableFuture中提供了一個異常處理方法exceptionally():

組合多個CompletableFuture

CompletableFuture中可以組合多個CompletableFuture,主要有如下兩種方法:

1. thenCompose方法

    public <U> CompletableFuture<U> thenCompose(
        Function<? super T, ? extends CompletionStage<U>> fn) {
        return uniComposeStage(null, fn);
    }

例子如下:

2. thenCombine方法

    public <U,V> CompletableFuture<V> thenCombine(
        CompletionStage<? extends U> other,
        BiFunction<? super T,? super U,? extends V> fn) {
        return biApplyStage(null, other, fn);
    }

例子如下:

支援timeout

在JDK9之後的CompletableFuture增加了timeout功能,如果任務在指定時間內沒有完成,則直接丟擲異常。

9.2 改進的讀寫鎖:StampedLock

StampedLock是JDK 8中引入的新的鎖機制,可以認為是讀寫鎖的一個改進版本,讀寫鎖雖然分離了讀和寫,使得讀與讀之間可以完全併發,但是讀和寫之間仍然是衝突的。讀鎖會阻塞寫鎖,它使用的仍然是悲觀的策略,如果有大量的讀執行緒,可能引起寫執行緒的飢餓。

stampedLock提供了一種樂觀的讀策略,這種樂觀的策略類似與無鎖的操作,使得樂觀鎖完全不會阻塞寫執行緒。

上面的例子中,首先試圖嘗試樂觀獲取鎖,方法會返回一個類似於時間戳的stamp,然後進行相應的讀取操作,當然為了保證沒有其他執行緒修改了x、y的值,需要呼叫validate方法來進行驗證,判斷這個stamp在讀過程中是否發生了修改。如果沒有修改,則直接進行接下來的計算,否則,升級樂觀鎖為悲觀鎖,使用readLock獲取讀鎖。如果當前有其他執行緒已經獲取了鎖,當前執行緒可能被掛起。

9.2 更快的原子類:LongAdder

JDK引入了LongAdder,對之前的atomicInteger的效能進行了增強,AtomicLong 的 Add() 是依賴自旋不斷的 CAS 去累加一個 Long 值。如果在競爭激烈的情況下,CAS 操作不斷的失敗,就會有大量的執行緒不斷的自旋嘗試 CAS 會造成 CPU 的極大的消耗。

對於同樣的一個 add() 操作,上文說到 AtomicLong 只對一個 Long 值進行 CAS 操作。而 LongAdder 是針對 Cell 陣列的某個 Cell 進行 CAS 操作 ,把執行緒的名字的 hash 值,作為 Cell 陣列的下標,然後對 Cell[i] 的 long 進行 CAS 操作。簡單粗暴的分散了高併發下的競爭壓力。

在實際的操作中,LongAdder並不會一開始就動用陣列進行處理,而是將所有資料都記錄在一個稱為base的變數中,如果在多執行緒的條件下,大家修改base沒有衝突,也沒有必要擴充套件成cell陣列,但是,一旦base修改發生衝突,就會初始化cell陣列,使用新的策略。如果使用cell陣列之後,發現在某一個cell上的更新依然存在衝突,那麼系統就會嘗試建立新的cell,以減少衝突。

AtomicLong可否可以被LongAdder替代?

有了傳說中更高效的LongAdder,那AtomicLong可否不使用了呢?當然不是!

答案就在LongAdder的java doc中,從我們翻譯的那段可以看出,LongAdder適合的場景是統計求和計數的場景,而且LongAdder基本只提供了add方法,而AtomicLong還具有cas方法(要使用cas,在不直接使用unsafe之外只能藉助AtomicXXX了)

LongAdder有啥用?
從java doc中可以看出,其適用於統計計數的場景,例如計算qps這種場景。在高併發場景下,qps這個值會被多個執行緒頻繁更新的,所以LongAdder很適合。


參考:

  • https://www.jianshu.com/p/22d38d5c8c2a
  • 《實戰Java高併發程式設計》

本文由『後端精進之路』原創,首發於部落格 http://teckee.github.io/ , 轉載請註明出處

搜尋『後端精進之路』關注公眾號,立刻獲取最新文章和價值2000元的BATJ精品面試課程。

相關推薦

Java併發程式設計系列-(9) JDK 8/9/10併發

9.1 CompletableFuture CompletableFuture是JDK 8中引入的工具類,實現了Future介面,對以往的FutureTask的功能進行了增強。 手動設定完成狀態 CompletableFuture和Future一樣,可以作為函式呼叫的契約,當向CompletableFut

Java併發程式設計系列-(8) JMM和底層實現原理

8. JMM和底層實現原理 8.1 執行緒間的通訊與同步 執行緒之間的通訊 執行緒的通訊是指執行緒之間以何種機制來交換資訊。在程式設計中,執行緒之間的通訊機制有兩種,共享記憶體和訊息傳遞。 在共享記憶體的併發模型裡,執行緒之間共享程式的公共狀態,執行緒之間通過寫-讀記憶體中的公共狀態來隱式進行通訊,典型的

併發程式設計系列:4種常用Java執行緒鎖的特點,效能比較、使用場景

高併發程式設計系列:4種常用Java執行緒鎖的特點,效能比較、使用場景 http://youzhixueyuan.com/4-kinds-of-java-thread-locks.html   在Java併發程式設計中,經常遇到多個執行緒訪問同一個 共享資源 ,這時候作為開發者

併發程式設計系列:ConcurrentHashMap的實現原理(JDK1.7和JDK1.8)

轉載自:https://www.toutiao.com/i6623301848268800519/ HashMap、CurrentHashMap 的實現原理基本都是BAT面試必考內容,阿里P8架構師談:深入探討HashMap的底層結構、原理、擴容機制深入談過hashmap的實現原理

Java 併發程式設計系列之閉鎖(CountDownLatch)

在講閉鎖之前,我們先來思考一個問題:在多執行緒環境下,主執行緒列印一句話,如何保證這句話最後(其他執行緒全部執行完畢)列印? 博主目前可以想到的實現方式有兩種。一種是通過 join() 方法實現,另一

Java 併發程式設計系列之帶你瞭解多執行緒

早期的計算機不包含作業系統,它們從頭到尾執行一個程式,這個程式可以訪問計算機中的所有資源。在這種情況下,每次都只能執行一個程式,對於昂貴的計算機資源來說是一種嚴重的浪費。 作業系統出現後,計算機可以執行多個程式,不同的程式在單獨的程序中執行。作業系統負責為各個獨

Java併發程式設計系列(一)避免死鎖

避免死鎖 (1)避免一個執行緒同時獲取多個鎖 (2)避免一個執行緒在鎖內佔用多個資源,儘量保證每個鎖只佔用一個資源 (3)使用定時鎖,使用lock.trylock(timeout)替代內部鎖機制 (4)

Java併發程式設計系列之十五 Executor框架

                     Java使用執行緒完成非同步任務是很普遍的事,而執行緒的建立與銷燬需要一定的開銷,如果每個任務都需要建立一個執行緒將會消耗大量的計算資源,JDK 5之後把工作單元和執行機制區分開了,工作單元包括Runnable和Callable,而執行機制則由Executor框架提供

Java併發程式設計系列之十七 Condition介面

                     通過前面的文章,我們知道任何一個Java物件,都擁有一組監視器方法,主要包括wait()、notify()、notifyAll()方法,這些方法與synchronized關鍵字配合使用可以實現等待/通知機制。而且前面我們已經使用這種方式實現了生產者-消費者模式。類似地

Java併發程式設計系列之十九:原子操作類

原子操作類簡介 當更新一個變數的時候,多出現資料爭用的時候可能出現所意想不到的情況。這時的一般策略是使用synchronized解決,因為synchronized能夠保證多個執行緒不會同時更新該變數。然而,從jdk 5之後,提供了粒度更細、量級更輕,並且在多核

java併發程式設計系列之CyclicBarrier的使用

在日常活動中,經常會遇到這樣一種場景:我們會約定在某個地點集合,等所有的組員都集合後,然後我們才開始活動,等活動結束後,所有的組員再一次集合,報道後再各自回家。在我們的程式中,也會經常遇到這種場景,需要所有的子執行緒都結束之後,再匯聚所有的結果,然後根據結果在決定下一步的操

java併發程式設計系列之ReadWriteLock讀寫鎖的使用

前面我們講解了Lock的使用,下面我們來講解一下ReadWriteLock鎖的使用,顧明思義,讀寫鎖在讀的時候,上讀鎖,在寫的時候,上寫鎖,這樣就很巧妙的解決synchronized的一個性能問題:讀與讀之間互斥。 ReadWriteLock也是一個介面,原型如下: pub

Java併發程式設計系列之二十七:ThreadLocal

ThreadLocal簡介 ThreadLocal翻譯過來就是執行緒本地變數,初學者可能以為ThreadLocal是指一個Thread,其實說白了,ThreadLocal就是一個成員變數,只不過這是一個特殊的變數——變數值總是與當前執行緒(呼叫Thread.c

Java併發程式設計系列之十四:阻塞佇列

阻塞佇列(BlockingQueue)是一個支援兩個附加操作的佇列。這兩個附加操作支援阻塞地插入和移除方法。支援阻塞插入的方法是指當佇列滿時會阻塞插入元素的執行緒,直到佇列不滿;支援阻塞移除的方法是指當佇列為空時獲取元素的執行緒無法繼續獲取元素直到佇列不空。

程式設計練習100例-8,9,10

#99乘法口訣表;要求1的一行。2的一行 for i in range(1,10): for j in range (1,10): print('%d * %d=% 2d '% (i,j,i*j),end='')# end讓1的結束以後另起一行 pr

Java併發程式設計系列之四:鎖與volatile的記憶體語義

前言 在前面的文章中已經提到過volatile關鍵字的底層實現原理:處理器的LOCK指令會使得其他處理器將快取重新整理到記憶體中(確切說是主存)以及會把其他處理器的快取設定為無效。這裡的記憶體語義則說的是在JMM中的實現,那麼為什麼要理解volatile和鎖在

Java併發程式設計系列之十二:死鎖、飢餓和活鎖

死鎖發生在一個執行緒需要獲取多個資源的時候,這時由於兩個執行緒互相等待對方的資源而被阻塞,死鎖是最常見的活躍性問題。這裡先分析死鎖的情形: 假設當前情況是執行緒A已經獲取資源R1,執行緒B已經獲取資源R2,之後執行緒A嘗試獲取資源R2,這個時候因為資源R2已經

Java併發程式設計系列之二十六 ConcurrentModificationException

                        在多執行緒程式的

Java併發程式設計系列之二十 Fork/Join框架

                        Fork/Joi

Java併發程式設計系列之十二 死鎖 飢餓和活鎖

                        死鎖發生在一個執