Java多线程中AtomicReference为何无法避免User.count的竞态条件?
Hey there, let's unpack what's going on here—you're not alone in mixing up what AtomicReference actually guarantees!
First, the core issue: You're using AtomicReference the wrong way
AtomicReference only ensures atomicity for the object reference itself, not the fields inside the object it points to. Let's break down your thread's operation step by step to see the race condition:
When each thread runs userRef.get().count += 1, it's actually doing three separate, non-atomic steps:
- Call
userRef.get()to fetch the current User object (this part is atomic, but that's where AtomicReference's job ends). - Read the current value of
countfrom that User object. - Increment the value and assign it back to
count(and since Integer is immutable, this creates a new Integer object under the hood).
Multiple threads can easily overlap during steps 2 and 3. For example:
- Thread A reads
count = 500 - Thread B reads
count = 500before Thread A finishes updating it - Both threads increment to 501 and assign it back
- The final
countis 501 instead of 502, leading to lost increments (or in edge cases, unexpected higher values due to thread scheduling quirks with reference reassignment).
Why sync/AtomicInteger works
- Synchronization: Wrapping the
count +=1operation in asynchronizedblock or method locks the entire sequence, ensuring only one thread can execute those three steps at a time. No overlaps, no lost updates. - AtomicInteger: This class is built specifically for atomic read-modify-write operations on integers. Methods like
incrementAndGet()handle the entire cycle as a single atomic action, eliminating any window for thread interference.
How to use AtomicReference correctly (if you want to stick with it)
If you want to keep using AtomicReference for this scenario, you need to treat the entire User object as immutable and replace it entirely each time you update count. Here's what that looks like:
// Inside your thread's run method: User currentUser; User updatedUser; do { currentUser = userRef.get(); // Create a new User object with the incremented count updatedUser = new User(currentUser.count + 1); // CAS operation: only update the reference if it still points to currentUser } while (!userRef.compareAndSet(currentUser, updatedUser));
This works because compareAndSet ensures we only replace the User reference if no other thread modified it since we fetched currentUser. If another thread beats us to it, we retry until we succeed.
To sum up
AtomicReference doesn't magically make all operations on the referenced object atomic—it only protects the act of swapping which object the reference points to. Your original code modified the internal state of the referenced User object, which falls outside AtomicReference's scope.
内容的提问来源于stack exchange,提问作者nayak0765

