0Pricing
Java Academy · レッスン

Atomic 変数:ロックなしの更新

ロックを使わずにスレッドセーフなカウンターを実現するため、AtomicInteger、AtomicLong、AtomicReference を使用します。

「Atomic 変数:ロックなしの更新」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。

非アトミック操作の問題

単純なインクリメントであるcount++はスレッドセーフではありません。これは読み取り、加算、書き込みという3つの操作で構成されます。複数のスレッドがこれらの操作に割り込むと、更新の消失が発生する可能性があります。アトミック変数を使えば、ロックなしでこの問題を解決できます。

// UNSAFE:
int count = 0;
// Thread A reads count=0, Thread B reads count=0
// Both write 1 — second update lost!
count++; // NOT thread-safe

// SAFE:
import java.util.concurrent.atomic.*;
AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet(); // atomic, lock-free

AtomicIntegerの基本

AtomicIntegerは、CPUのcompare-and-swap(CAS)命令を使用して、アトミックであることが保証された整数操作を提供します。ロックは必要ありません。

AtomicInteger counter = new AtomicInteger(0);

counter.set(10);                   // set to 10
System.out.println(counter.get()); // 10

int prev = counter.getAndIncrement(); // returns 10, sets to 11
int curr = counter.incrementAndGet(); // sets to 12, returns 12
counter.addAndGet(5);                 // adds 5, returns 17
counter.decrementAndGet();            // subtracts 1, returns 16

compareAndSet: CAS操作

compareAndSet(expected, update)は、現在の値がexpectedと等しい場合にのみ、値をアトミックに更新します。成功するとtrueを返します。これは、すべてのロックフリーアルゴリズムの基盤です。

AtomicInteger ai = new AtomicInteger(5);

boolean updated = ai.compareAndSet(5, 10); // current==5? set to 10
System.out.println(updated); // true
System.out.println(ai.get()); // 10

boolean failed = ai.compareAndSet(5, 20); // current==10, not 5 → fails
System.out.println(failed); // false
System.out.println(ai.get()); // still 10

カウンター用のAtomicLong

AtomicLongは64ビット版に相当します。シーケンス生成器やヒットカウンターでよく使用されます:

AtomicLong seq = new AtomicLong(0);

// Thread-safe sequence generator:
long nextId() {
    return seq.incrementAndGet();
}

System.out.println(nextId()); // 1
System.out.println(nextId()); // 2
System.out.println(nextId()); // 3

AtomicBoolean

スレッドセーフなブール値フラグです。1回限りの初期化やシャットダウンシグナルによく使用されます:

AtomicBoolean initialized = new AtomicBoolean(false);

void initOnce() {
    // compareAndSet: only first caller succeeds
    if (initialized.compareAndSet(false, true)) {
        System.out.println("Initializing...");
        // perform expensive init
    } else {
        System.out.println("Already initialized");
    }
}

AtomicReference

AtomicReference<V>は、オブジェクト参照のアトミックな更新を提供します。ロックフリーなデータ構造の実装に使用されます:

import java.util.concurrent.atomic.*;

record Config(String host, int port) {}

AtomicReference<Config> configRef =
    new AtomicReference<>(new Config("localhost", 8080));

// Hot-swap the configuration atomically:
Config oldConfig = configRef.get();
Config newConfig = new Config("prod.example.com", 443);

boolean swapped = configRef.compareAndSet(oldConfig, newConfig);
System.out.println(swapped); // true
System.out.println(configRef.get()); // Config[host=prod.example.com, port=443]

updateAndGet / getAndUpdate

内部でCASの再試行ループを使用し、関数をアトミックに適用します:

AtomicInteger value = new AtomicInteger(10);

// Apply function atomically: square the value
int newVal = value.updateAndGet(x -> x * x);
System.out.println(newVal); // 100

// Get old, then apply:
int old = value.getAndUpdate(x -> x + 1);
System.out.println(old);   // 100
System.out.println(value.get()); // 101

accumulateAndGet

関数を使用して、現在の値と指定した値を結合します:

AtomicInteger max = new AtomicInteger(0);

// Keep running maximum:
max.accumulateAndGet(42, Math::max); // max(0, 42) = 42
max.accumulateAndGet(15, Math::max); // max(42, 15) = 42
max.accumulateAndGet(99, Math::max); // max(42, 99) = 99

System.out.println(max.get()); // 99

高競合カウンター用のLongAdder

スレッドの競合が激しい場合、AtomicLongのCAS再試行によってパフォーマンスが低下する可能性があります。LongAdderは複数のセルを使用して競合を減らします:

import java.util.concurrent.atomic.*;

LongAdder adder = new LongAdder();

// Multiple threads can call add() with minimal contention:
adder.increment();
adder.add(5);
adder.increment();

System.out.println(adder.sum()); // 7
// Note: sum() is NOT atomic with add() — use only when no concurrent adds

アトミック配列

AtomicIntegerArray / AtomicLongArrayは、配列の個々の要素に対するアトミック操作を提供します:

AtomicIntegerArray arr = new AtomicIntegerArray(5);

arr.set(0, 10);
arr.incrementAndGet(0);
arr.compareAndSet(2, 0, 42); // index 2: if 0, set 42

System.out.println(arr.get(0)); // 11
System.out.println(arr.get(2)); // 42

アトミック変数とロックの使い分け

次のような場合はアトミック変数を使用します:

  • 単一変数を更新する場合(カウンター、フラグ、参照)
  • 競合が少ない場合
  • ロックフリーのパフォーマンスが重要な場合

次のような場合はロックを使用します:

  • 複数の変数をアトミックに更新する必要がある場合
  • 複雑な条件付きロジックが必要な場合
  • 競合が非常に激しい場合(CASの再試行でパフォーマンスが低下するため)

クイックチェック

compareAndSet(expected, update)は何を保証しますか?

まとめ: アトミック変数

重要なポイント:

  • アトミック型はCPUのCAS命令を使用します。ロックフリーでスレッドセーフです
  • AtomicInteger/Long/Boolean/Referenceは、単一変数のアトミック性に使用します
  • compareAndSetはロックフリーアルゴリズムの基盤です
  • updateAndGet/accumulateAndGetは関数型の更新に使用します
  • LongAdderは競合が激しいカウンターの処理に使用します

よくある質問

「Atomic 変数:ロックなしの更新」レッスンは無料ですか?

はい。「Atomic 変数:ロックなしの更新」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。

「Atomic 変数:ロックなしの更新」で何を学びますか?

ロックを使わずにスレッドセーフなカウンターを実現するため、AtomicInteger、AtomicLong、AtomicReference を使用します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Java Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「Atomic 変数:ロックなしの更新」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJava Academyレッスンでコードを書いて実行できますか?

はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ReentrantLock と synchronized の比較
  2. 読み取り・書き込み処理のための ReadWriteLock
  3. Atomic 変数:ロックなしの更新
  4. StampedLock と楽観的読み取り
← Java Academyに戻る