java.util.concurrentの強化

java.util.concurrentパッケージにも新たなクラスがいくつか追加されている。たとえば従来のJava SEでは複数スレッドからアトミックに更新可能なデータ型としてAtomicIntegerといったクラスが用意されていたが、Java 8では新たにLongAdderやDoubleAdderといったクラスが追加されている。これらのクラスは複数スレッドから頻繁に更新を行うようなケースで高いパフォーマンスを発揮する。

// LongAdderを生成(初期値は0)
LongAdder longAdder = new LongAdder();
// 加算
longAdder.add(100);
// インクリメント
longAdder.increment();
// デクリメント
longAdder.decrement();
// 値をlong型で取得
long value = longAdder.longValue();

また、新たにjava.util.concurrent.locks.StampedLockが追加されている。これはマルチスレッド環境下でもロックをせずに一貫性のある読み取り処理を行う「楽観的読み取り」を実現するものだ。リードロックの取得コストが不要になるのはもちろんのこと、リードロックによって書き込み処理がブロックされることがなくなるため読み取り・書き込み双方の高速化が期待できる。

以下のように読み取り処理の開始前にスタンプを取得しておき、読み取り処理の終了後にスタンプを使用して読み取り処理中に別スレッドからの更新処理が行われていないことを確認する。もし読み取り処理中に別スレッドからの更新処理が行われていた場合はプログラム側でリカバリ処理(以下の例ではリードロックを取得したうえで再度読み取り処理を行っている)を行う必要がある。

public class StampedLockSample {

  private final StampedLock lock = new StampedLock();

  private int a;
  private int b;

  public void setValues(int a, int b){
    // 書き込みロックを取得
    long stamp = lock.writeLock();

    // 値を更新
    this.a = a;
    this.b = b;

    // 書き込みロックを解放
    lock.unlockWrite(stamp);
  }

  public int calculate(){
    // 楽観的読み取りのためのスタンプを取得
    long stamp = lock.tryOptimisticRead();

    // 値を読み取り
    int currentA = this.a;
    int currentB = this.b;

    // 他のスレッドから値が更新されていないことを確認
    if(!lock.validate(stamp)){
      // 他のスレッドから値が更新されていた場合は読み取りロックを取得して読み取り
      stamp = lock.readLock();

      currentA = this.a;
      currentB = this.b;

      // 読み取りロックを解放
      lock.unlockRead(stamp);
    }

    return currentA + currentB;
  }
}