杰瑞科技汇

AtomicInteger如何保证原子性?

Of course! Let's dive deep into java.util.concurrent.atomic.AtomicInteger.

AtomicInteger如何保证原子性?-图1
(图片来源网络,侵删)

What is AtomicInteger?

AtomicInteger is a class in Java's java.util.concurrent.atomic package. It provides an int value that can be updated atomically.

"Atomically" is the key word here. It means that a series of operations (like reading, modifying, and writing) are performed as a single, indivisible unit. Once an atomic operation starts, it cannot be interrupted by other threads. This guarantees that the operation completes in its entirety before any other thread can access the shared variable.

Why Do We Need It?

In multi-threaded applications, when multiple threads read and write to a shared primitive variable (like int), you face classic concurrency problems like race conditions.

Consider this simple, non-atomic code:

AtomicInteger如何保证原子性?-图2
(图片来源网络,侵删)
class Counter {
    private int count = 0;
    public void increment() {
        count++; // This is NOT an atomic operation!
    }
    public int getCount() {
        return count;
    }
}

The count++ statement might look like a single action, but it's actually three separate operations:

  1. Read the current value of count.
  2. Increment that value.
  3. Write the new value back to count.

If two threads (Thread A and Thread B) try to execute increment() at the same time, this is a possible scenario:

  1. Thread A reads count (value is 0).
  2. Thread B reads count (value is still 0).
  3. Thread A increments its read value (0 + 1 = 1).
  4. Thread B increments its read value (0 + 1 = 1).
  5. Thread A writes 1 back to count.
  6. Thread B writes 1 back to count.

The Result: The count is now 1, but it should have been 2. This is a race condition.

AtomicInteger solves this problem by ensuring that read-modify-write operations are performed atomically, preventing such race conditions.

AtomicInteger如何保证原子性?-图3
(图片来源网络,侵删)

How AtomicInteger Works: The "Magic"

AtomicInteger uses a sophisticated technique called Compare-And-Swap (CAS), which is a hardware-level instruction supported by most modern processors.

Here's a simplified view of how a CAS operation works:

  1. Read: Get the current value of the variable from memory (let's call it V).
  2. Compare: Compare V with an expected value (let's call it E). The CAS operation succeeds only if V is equal to E.
  3. Swap: If they are equal, atomically update the variable's value in memory to a new value (let's call it N). If they are not equal, do nothing and let the caller try again.

This entire read-compare-swap sequence is a single, non-interruptible hardware instruction.

Let's see how AtomicInteger's incrementAndGet() would work using CAS:

  1. Thread A wants to increment. It reads the current value V (e.g., 5).
  2. It sets its expected value E to 5 and the new value N to 6.
  3. It performs a CAS operation: "If the value in memory is 5, set it to 6."
  4. The hardware checks the memory. The value is indeed 5. It's updated to 6. Thread A's operation succeeds.
  5. Meanwhile, Thread B also wants to increment. It reads the current value V (which is now 6).
  6. It sets its expected value E to 6 and the new value N to 7.
  7. It performs a CAS operation: "If the value in memory is 6, set it to 7."
  8. The hardware checks the memory. The value is 6. It's updated to 7. Thread B's operation succeeds.

The Key Advantage: CAS is lock-free. Unlike traditional synchronized blocks, which can block threads, CAS allows threads to keep trying (looping) until they successfully update the variable. This can lead to better performance under high contention, as it avoids the overhead of thread context switching.


Common Methods and Examples

Here are the most frequently used methods in AtomicInteger.

Basic Get and Set

These methods are straightforward and thread-safe.

AtomicInteger atomicInt = new AtomicInteger(10);
// Get the current value
int currentValue = atomicInt.get(); // currentValue is 10
// Set a new value
atomicInt.set(20);
System.out.println(atomicInt.get()); // Prints 20

Atomic "Read-Modify-Write" Operations

These are the most powerful methods, as they guarantee atomicity.

AtomicInteger atomicInt = new AtomicInteger(5);
// Atomically increments by 1 and returns the new value
int newValue1 = atomicInt.incrementAndGet(); // newValue1 is 6, atomicInt is 6
// Atomically decrements by 1 and returns the new value
int newValue2 = atomicInt.decrementAndGet(); // newValue2 is 5, atomicInt is 5
// Atomically adds the given value and returns the new value
int newValue3 = atomicInt.addAndGet(10); // newValue3 is 15, atomicInt is 15

Conditional Updates (The CAS Core)

These methods attempt an update and return a boolean indicating success or failure.

AtomicInteger atomicInt = new AtomicInteger(10);
// Atomically sets the value to 15 if the current value is 10
boolean success = atomicInt.compareAndSet(10, 15);
System.out.println("Update successful? " + success); // Prints true
System.out.println("Current value: " + atomicInt.get()); // Prints 15
// Try to set it to 20, but the current value is 15, not 10
boolean failure = atomicInt.compareAndSet(10, 20);
System.out.println("Update successful? " + failure); // Prints false
System.out.println("Current value: " + atomicInt.get()); // Prints 15 (unchanged)

Lazy-Set (for Performance)

lazySet() is a performance optimization. It sets the value without enforcing any happens-before ordering with subsequent reads. This is useful in scenarios where you don't need other threads to see the update immediately, reducing memory visibility overhead.

atomicInt.lazySet(100); // Sets the value, but a read might not see it immediately.

The Atomic vs. synchronized Example

Let's see AtomicInteger in action in our Counter class.

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class AtomicCounter {
    // The shared counter is now an AtomicInteger
    private final AtomicInteger count = new AtomicInteger(0);
    public void increment() {
        // This operation is atomic and thread-safe
        count.incrementAndGet();
    }
    public int getCount() {
        return count.get();
    }
}
public class AtomicIntegerDemo {
    public static void main(String[] args) throws InterruptedException {
        AtomicCounter counter = new AtomicCounter();
        int threadCount = 1000;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        // Launch 1000 threads, each incrementing the counter 1000 times
        for (int i = 0; i < threadCount; i++) {
            executor.submit(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.increment();
                }
            });
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        System.out.println("Final count: " + counter.getCount());
        // The output will be exactly 1,000,000. No race condition!
    }
}

When to Use AtomicInteger vs. synchronized

Feature AtomicInteger synchronized
Granularity Fine-grained. Operates on a single variable. Coarse-grained. Can protect a block of code or an entire method.
Performance Generally better under low to moderate contention. Lock-free. Can be more efficient under high contention for complex operations, as it avoids the "retry" loop of CAS.
Use Case Perfect for simple, independent atomic operations on a single primitive or reference variable (AtomicBoolean, AtomicLong, AtomicReference). Best for protecting a sequence of multiple operations that must be executed together without interference.
Readability Can be less intuitive for complex logic. Very clear and explicit about the protected section of code.

Summary

  • AtomicInteger provides a thread-safe int wrapper.
  • It uses Compare-And-Swap (CAS) to achieve atomicity without traditional locks, making it lock-free.
  • It's ideal for performing simple, atomic operations like increment, decrement, and conditional updates on
分享:
扫描分享到社交APP
上一篇
下一篇