Java並行処理 - CAS操作、アトミッククラス、ABA問題、Callableインタフェース、ReentrantLock

CAS操作の概要

CAS(Compare-And-Swap)は並行処理における重要な非ブロッキングアルゴリズムであり、データ整合性を保ちながらスレッドの効率的な実行を実現します。この操作はCPU命令レベルで実装されており、アトミックな特性を持っています。

CASプロセスでは、メモリ上の値が期待値と一致するか確認し、一致すれば新しい値に置換します。不一致の場合は操作が失敗します。

boolean compareAndSwap(address, expected, newValue) {
    if (*address == expected) {
        *address = newValue;
        return true;
    }
    return false;
}

主要なパラメータ:

  • address:更新対象となるメモリ位置
  • expected:期待される現在値
  • newValue:設定したい新しい値

アトミッククラスの利用

CAS操作の主な応用としてアトミッククラスがあります。java.util.concurrent.atomicパッケージ配下にあるこれらのクラスはロックなしでスレッドセーフな操作を提供し、従来の同期メカニズムより優れたパフォーマンスを発揮します。

以下はアトミック整数の使用例です:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    public static void main(String[] args) throws InterruptedException {
        SafeCounter counter = new SafeCounter();
        
        Thread worker1 = new Thread(() -> {
            for (int idx = 0; idx < 50000; idx++) {
                counter.incrementValue();
            }
        });

        Thread worker2 = new Thread(() -> {
            for (int idx = 0; idx < 50000; idx++) {
                counter.incrementValue();
            }
        });

        worker1.start();
        worker2.start();
        worker1.join();
        worker2.join();

        System.out.println(counter.getValue());
    }
}

class SafeCounter {
    private final AtomicInteger atomicValue = new AtomicInteger(0);
    
    public void incrementValue() {
        atomicValue.incrementAndGet(); // 前置インクリメント相当
        // atomicValue.getAndAdd(delta); // 加算操作も可能
    }
    
    public int getValue() {
        return atomicValue.get();
    }
}

アトミッククラスの内部動作

アトミック操作の基本的な実装パターン:

class CustomAtomicInteger {
    private volatile int currentValue;

    public int incrementOperation() {
        int tempValue;
        int updatedValue;
        do {
            tempValue = currentValue;
            updatedValue = tempValue + 1;
        } while (!performCAS(currentValue, tempValue, updatedValue));
        return tempValue;
    }

    private boolean performCAS(int memoryAddress, int expected, int replacement) {
        // JVM内部でのネイティブ実装
        return unsafeCompareAndSwap(memoryAddress, expected, replacement);
    }
}

スピンロックのような無限ループにより、競合状況でも正しく処理を完了させます。

スピンロックのCAS実装

以下はCASベースのスピンロック実装:

public class NonBlockingLock {
    private volatile Thread holderThread = null;
    
    public void acquireLock() {
        Thread current = Thread.currentThread();
        while (!compareAndSet(holderThread, null, current)) {
            // ロック取得まで待機
        }
    }

    public void releaseLock() {
        if (Thread.currentThread().equals(holderThread)) {
            holderThread = null;
        }
    }
    
    private boolean compareAndSet(Thread expected, Thread newValue) {
        // 実際にはUnsafeクラスを使用
        return true; // シンプル化のため
    }
}

ABA問題の詳細

CAS操作における潜在的な問題点としてABA問題が挙げられます。ある変数がA→B→Aと変化した場合、変化していないと誤認識され、予期せぬ動作を引き起こす可能性があります。

例えば銀行口座の残高処理において:

  • 初期残高:1000円
  • 引き出し要求:500円
  • 同時アクセス中に他のスレッドが入金処理(+500円)
  • 最終的に不正な残高計算が発生

この問題を解決するためにはバージョン番号やタイムスタンプの導入が必要です。

Callableインタフェース

Runnableに似た機能を持つCallableは戻り値を持つタスクを定義できます。FutureTaskを通じてThreadで実行可能です。

import java.util.concurrent.*;

public class CallableDemo {
    public static void main(String[] args) throws Exception {
        TaskWithResult task = new TaskWithResult();
        FutureTask<Integer> executionWrapper = new FutureTask<>(task);
        
        Thread executor = new Thread(executionWrapper);
        executor.start();
        
        // 結果取得(ブロッキング)
        Integer result = executionWrapper.get();
        System.out.println("計算結果: " + result);
    }
}

class TaskWithResult implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int num = 1; num <= 100; num++) {
            sum += num;
        }
        return sum;
    }
}

ReentrantLockの特徴

Java標準ライブラリの再入可能ロック実装で、synchronizedキーワードに比べ柔軟な制御が可能です。

主な違い:

  • synchronizedはJVM内部実装、ReentrantLockはJavaクラスとして提供
  • 明示的なlock/unlock呼び出しが必要
  • tryLockメソッドによるタイムアウト指定や即時返却が可能
  • 公平ロック・非公平ロックの選択肢あり
  • Conditionオブジェクトによる高度な同期制御
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private static int sharedCounter = 0;
    private static final ReentrantLock mutex = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread processor1 = new Thread(() -> {
            for (int iteration = 0; iteration < 50000; iteration++) {
                mutex.lock();
                try {
                    sharedCounter++;
                } finally {
                    mutex.unlock();
                }
            }
        });

        Thread processor2 = new Thread(() -> {
            for (int iteration = 0; iteration < 50000; iteration++) {
                mutex.lock();
                try {
                    sharedCounter++;
                } finally {
                    mutex.unlock();
                }
            }
        });

        processor1.start();
        processor2.start();
        processor1.join();
        processor2.join();

        System.out.println("最終カウント: " + sharedCounter);
    }
}

タグ: Java Multithreading cas atomic-operations ReentrantLock

9月6日 07:47 投稿