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-freeAtomicIntegerの基本
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 16compareAndSet: 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()); // 3AtomicBoolean
スレッドセーフなブール値フラグです。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()); // 101accumulateAndGet
関数を使用して、現在の値と指定した値を結合します:
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フィードバックを取得できます。ローカル設定は不要です。