Javaの並行処理シリーズ - (9)同時8/9/10 JDKで

9.1 CompletableFuture

CompletableFutureツールJDK 8は、従来のFutureTaskの機能が強化され、今後のインターフェースを実現するために導入されます。

手動では、完了ステータスを設定しました

データの準備ができていない場合は、CompletableFutureからデータを要求するときに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は、非同期実行するための便利なインターフェイスを提供し、

Screen Shot 2019-12-15 at 3.25.31 PM.png

戻り値はメソッドsupplyAsyncシーンのために必要であり; runAsync方法は、シーンのために値を返しません。どちらの方法が許容エグゼキュータのパラメータであることに注意してください、あなたは簡単に指定されたスレッドプール内のタスクの実行を行うことができます。

Screen Shot 2019-12-15 at 3.30.00 PM.png

ストリーミングコール

CompletableFuture()は、非同期タスクを実行する操作がリスト内のJDK 8、次の例では、まずsupplyAsyncと同様であり、ストリーミング、および、タスク・フロー処理動作の結果を使用して提供します。

Screen Shot 2019-12-15 at 3.38.56 PM.png

注最終面には、そうでない場合はCompletableFuture非同期実行により、主な機能は計算がメインスレッドの終了と、終了完了するのを待たず、結果を得るために使用されるgetメソッドを呼び出すことを、すべてのデーモンスレッドが引き起こして、終了します計算が正常に完了することができません。

例外処理

同一の機能プログラミングの実装において遭遇CompletableFuture異常)は(例外は例外CompletableFuture処理方法が提供され、例外処理を扱うために利用することができます。

Screen Shot 2019-12-15 at 3.47.19 PM.png

複数CompletableFutureを組み合わせます

CompletableFutureが複数のCompletableFutureを組み合わせることができ、次の2つの方法があります。

1. thenCompose方法

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

例としては、次のとおりです:

Screen Shot 2019-12-15 at 3.51.46 PM.png

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);
    }

例子如下:

Screen Shot 2019-12-15 at 3.53.40 PM.png

支持timeout

在JDK9之后的CompletableFuture增加了timeout功能,如果任务在指定时间内没有完成,则直接抛出异常。

Screen Shot 2019-12-15 at 3.56.31 PM.png

9.2 改进的读写锁:StampedLock

StampedLock是JDK 8中引入的新的锁机制,可以认为是读写锁的一个改进版本,读写锁虽然分离了读和写,使得读与读之间可以完全并发,但是读和写之间仍然是冲突的。读锁会阻塞写锁,它使用的仍然是悲观的策略,如果有大量的读线程,可能引起写线程的饥饿。

stampedLock提供了一种乐观的读策略,这种乐观的策略类似与无锁的操作,使得乐观锁完全不会阻塞写线程。

Screen Shot 2019-12-15 at 4.16.12 PM.png

上面的例子中,首先试图尝试乐观获取锁,方法会返回一个类似于时间戳的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 操作。简单粗暴的分散了高并发下的竞争压力。

Screen Shot 2019-12-15 at 5.07.54 PM.png

在实际的操作中,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精品面试课程

后端精进之路.png

おすすめ

転載: www.cnblogs.com/way2backend/p/12141960.html