杰瑞科技汇

AtomicInteger在Java中如何保证线程安全?

Of course! Let's dive deep into AtomicInteger in Java.

AtomicInteger在Java中如何保证线程安全?-图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, meaning it's thread-safe without requiring explicit synchronization.

Think of it as a thread-safe version of the primitive int or the Integer wrapper class. Operations like increment, decrement, and compare-and-set are guaranteed to be performed as a single, indivisible step, even when multiple threads are accessing it concurrently.


The Core Problem it Solves: Race Conditions

Imagine you have a simple counter shared between multiple threads:

// NOT THREAD-SAFE CODE
class Counter {
    private int count = 0;
    public void increment() {
        count++; // This is NOT an atomic operation!
    }
    public int getCount() {
        return count;
    }
}

The line count++ looks simple, but it's actually three separate operations:

AtomicInteger在Java中如何保证线程安全?-图2
(图片来源网络,侵删)
  1. Read the current value of count.
  2. Increment that value by 1.
  3. Write the new value back to count.

If two threads (Thread A and Thread B) read the value at the same time, they both see 0. They both increment to 1, and both write 1 back. The result is 1, but it should have been 2. This is a race condition.

AtomicInteger solves this by making the entire "read-modify-write" sequence atomic.


Key Methods and How They Work

The power of AtomicInteger comes from its methods that use low-level, hardware-supported instructions (like Compare-And-Swap, or CAS) to guarantee atomicity.

Basic Atomic Operations

  • int get(): Gets the current value.
  • void set(int newValue): Sets the value to newValue.
  • int getAndIncrement(): Atomically increments the current value and returns the old value.
  • int incrementAndGet(): Atomically increments the current value and returns the new value.
  • int getAndDecrement(): Atomically decrements the current value and returns the old value.
  • int decrementAndGet(): Atomically decrements the current value and returns the new value.
  • int getAndAdd(int delta): Atomically adds the given value to the current value and returns the old value.
  • int addAndGet(int delta): Atomically adds the given value to the current value and returns the new value.

Example:

AtomicInteger在Java中如何保证线程安全?-图3
(图片来源网络,侵删)
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(10);
        System.out.println("Initial value: " + atomicInt.get()); // 10
        // Atomically increments and returns the new value
        int newValue = atomicInt.incrementAndGet();
        System.out.println("After incrementAndGet: " + newValue); // 11
        // Atomically adds 5 and returns the old value
        int oldValue = atomicInt.getAndAdd(5);
        System.out.println("Old value before addAndGet(5): " + oldValue); // 11
        System.out.println("New value after addAndGet(5): " + atomicInt.get()); // 16
    }
}

The "King" of Atomic Operations: compareAndSet (CAS)

This is the most powerful and fundamental method in AtomicInteger. It's the engine behind most of its other methods.

  • boolean compareAndSet(int expect, int update): Atomically sets the value to update if the current value is equal to expect. It returns true if the update was successful, and false otherwise.

This is an optimistic lock. It says, "I expect the value to be X. If it is, I'll change it to Y. If another thread changed it first, I'll get false and can try again."

Analogy: Imagine you're reserving a book at a library. You go to the librarian and say, "I want to reserve Atomic Habits (expect), and I'll take the last copy (update)." The librarian checks the shelf. If the book is there, they give it to you and mark it as reserved (returns true). If someone else just took the last copy a second ago, the librarian tells you it's gone (returns false), and you have to decide what to do next.

Example:

import java.util.concurrent.atomic.AtomicInteger;
public class CasExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(100);
        // Thread 1: Tries to set the value to 101 if it's currently 100
        boolean success1 = atomicInt.compareAndSet(100, 101);
        System.out.println("CAS 1 success? " + success1); // true
        System.out.println("Current value: " + atomicInt.get()); // 101
        // Thread 2: Tries to set the value to 102 if it's currently 100 (it's not)
        boolean success2 = atomicInt.compareAndSet(100, 102);
        System.out.println("CAS 2 success? " + success2); // false
        System.out.println("Current value is still: " + atomicInt.get()); // 101
    }
}

Other Useful Methods

  • int getAndUpdate(UnaryOperator<Integer> updateFunction): Atomically updates the current value with the results of applying the given function.
  • int updateAndGet(UnaryOperator<Integer> updateFunction): Atomically updates the current value with the results of applying the given function and returns the new value.
  • int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction): Atomically updates the current value by applying the given function to the current and given values, returning the previous value.
  • int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction): Atomically updates the current value by applying the given function to the current and given values, returning the new value.

These are modern, functional-style alternatives to the getAndAdd style methods.


When to Use AtomicInteger?

You should use AtomicInteger when you have a single mutable variable that is shared across multiple threads and you need to perform simple atomic operations on it.

Good Use Cases:

  1. Counters: Counting the number of events processed by multiple threads.
  2. Generators: Generating unique sequence numbers or IDs (e.g., getAndIncrement()).
  3. Simple Flags or States: Managing a state that can transition between a few integer values atomically.

Example: A Thread-Safe Counter

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class SafeCounter {
    // The AtomicInteger ensures all operations on count are thread-safe
    private final AtomicInteger count = new AtomicInteger(0);
    public void increment() {
        count.incrementAndGet();
    }
    public int getCount() {
        return count.get();
    }
    public static void main(String[] args) throws InterruptedException {
        SafeCounter counter = new SafeCounter();
        ExecutorService executor = Executors.newFixedThreadPool(10);
        // Submit 1000 tasks to the counter
        for (int i = 0; i < 1000; i++) {
            executor.submit(counter::increment);
        }
        // Shutdown the executor and wait for tasks to finish
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        // The final count will be exactly 1000
        System.out.println("Final count: " + counter.getCount()); // Correctly prints 1000
    }
}

AtomicInteger vs. synchronized

Feature AtomicInteger synchronized block/method
Granularity Fine-grained. Locks only the specific variable. Coarse-grained. Locks an entire block of code or object.
Performance Generally higher performance under low-to-medium contention because it uses non-blocking algorithms (CAS). Can be slower under high contention as threads may block and wait for the lock.
Complexity Simple for single-variable operations. Can become complex with compound logic (e.g., if-then-else on an atomic value). More straightforward for complex, multi-step operations.
Deadlocks Cannot deadlock. Can deadlock if not used carefully (e.g., acquiring multiple locks in different orders).
Use Case Ideal for single, mutable primitive variables (counters, flags). Ideal for protecting a critical section of code that involves multiple shared variables or complex logic.

Conclusion

AtomicInteger is a powerful and highly efficient tool for building concurrent applications in Java. It provides a simple, thread-safe way to manage shared integer state without the overhead and complexity of traditional locks.

Key Takeaways:

  • Use AtomicInteger for simple, atomic operations on shared integer counters or flags.
  • Its core mechanism is compareAndSet (CAS), a non-blocking, optimistic locking strategy.
  • It generally outperforms synchronized for simple operations, especially when contention is low.
  • For complex logic involving multiple shared variables, a synchronized block is often a clearer and safer choice.
分享:
扫描分享到社交APP
上一篇
下一篇