PR

外部の変数へのアクセス

 最後にもう1つクイズです。リスト10のコードには悪い点があるのですが、どこでしょう。

リスト10 合計処理

    private long total;
      
    private long sum(List<Long> numbers) {
        total = 0L;
        
        numbers.parallelStream()
               .forEach(x -> total += x);
        
        return total;
    }

 もちろん、問題は変数totalの存在です。

 ラムダ式から外部のローカル変数にアクセスするには、finalもしくは実質的finalでないといけません。このため、リスト10ではフィールドとしてtotalを定義しています。これでラムダ式からもアクセスすることができます。

 しかし、問題は変数totalがスレッドセーフではないということです。

 パラレルストリームを使用しているということは、変数totalは複数のスレッドからアクセスされることを意味します。しかし、変数totalはスレッドセーフではないため、演算が正しく行われません。

 では、変数totalの代わりに、アトミックに加算を行うことができるjava.util.concurrent.atomic.LongAdderクラスを使用してみたらどうでしょう。

リスト11 LongAdderクラスを使用した合計処理

    private long sum(List<Long> numbers) {
        LongAdder adder = new LongAdder();
        
        numbers.parallelStream()
               .forEach(x -> adder.add(x));
        
        return adder.longValue();
    }

 LongAdderクラスを使用すれば、複数のスレッドから加算を行うことが可能です。しかし、問題は、複数のスレッドからのアクセスがLongAdderオブジェクトに集中するため、LongAdderオブジェクトへのアクセスがボトルネックになってしまうことにあります。

 このようにパラレルストリームから外部の変数にアクセスすることは、パフォーマンスの劣化になりがちです。また、この例ではLongAdderクラスを用いることでロックや同期の処理を省略できましたが、一般的にはロックや同期の処理が必須になります。これらの処理はパラレル処理の知識が必要になり、バグも混入しやすくなります。