由全线程安全类构成的程序为何不具备线程安全性?请举例说明
Great question! This is one of those counterintuitive concurrency concepts that trips up even experienced developers. Let's break down why this happens, then look at concrete Java examples to make it click.
The Core Issue: Atomicity of Composed Operations
Thread-safe classes (like ConcurrentHashMap, AtomicInteger, or Vector) guarantee that individual method calls are atomic and thread-safe. But when you combine multiple method calls into a single logical operation—what's often called a "check-then-act" or "read-modify-write" sequence—this combined sequence is not automatically atomic. Other threads can interrupt between the individual method calls, leading to inconsistent state.
Example 1: Unsafe Combination with ConcurrentHashMap
Let's use ConcurrentHashMap (a thread-safe collection) to show this problem. Suppose we want to update a value in the map: if the key doesn't exist, we add it; if it does, we increment it. Here's a naive implementation that looks safe but isn't:
import java.util.concurrent.ConcurrentHashMap; public class ThreadSafeClassButUnsafeProgram { private final ConcurrentHashMap<String, Integer> scoreMap = new ConcurrentHashMap<>(); // This method is NOT thread-safe, even though scoreMap is thread-safe public void updateScore(String player, int points) { // Step 1: Thread-safe get() Integer currentScore = scoreMap.get(player); // Thread switch could happen here! // Step 2: Thread-safe put() if (currentScore == null) { scoreMap.put(player, points); } else { scoreMap.put(player, currentScore + points); } } }
What Goes Wrong?
Imagine two threads call updateScore("Alice", 10) at the same time, when Alice has no score yet:
- Thread 1 calls
get("Alice")and getsnull. - Before Thread 1 can call
put(), the JVM switches to Thread 2. - Thread 2 also calls
get("Alice")and getsnull. - Thread 2 calls
put("Alice", 10), setting Alice's score to 10. - The JVM switches back to Thread 1, which also calls
put("Alice", 10).
Instead of Alice having a score of 20 (the expected result), her score ends up as 10. The two thread-safe method calls don't form an atomic combined operation.
Example 2: Unsafe Usage of AtomicInteger
Even atomic classes like AtomicInteger can lead to unsafe programs if you misuse them. Let's say we want to increment a counter:
import java.util.concurrent.atomic.AtomicInteger; public class UnsafeAtomicUsage { private final AtomicInteger counter = new AtomicInteger(0); // This method is NOT thread-safe public void incrementCounter() { // Thread-safe get() int current = counter.get(); // Thread switch could happen here! // Thread-safe set() counter.set(current + 1); } }
The Problem Here
Two threads could both call get() and get the value 0, then both call set(1). The counter ends up at 1 instead of 2—even though every individual method call is thread-safe.
How to Fix It
The solution is to ensure that the entire logical operation is atomic. You have two main options:
- Use a synchronized block to wrap the combined operations:
public synchronized void updateScore(String player, int points) { Integer currentScore = scoreMap.get(player); if (currentScore == null) { scoreMap.put(player, points); } else { scoreMap.put(player, currentScore + points); } } - Use atomic methods provided by the thread-safe class itself (when available). For
ConcurrentHashMap, usecompute()which handles the combined operation atomically:public void updateScore(String player, int points) { scoreMap.compute(player, (key, currentScore) -> currentScore == null ? points : currentScore + points); } - For
AtomicInteger, use the built-in atomic increment method instead of combiningget()andset():public void incrementCounter() { counter.incrementAndGet(); // Atomic and thread-safe }
Key Takeaway
Thread-safe classes protect their own internal state, but they can't protect the logical consistency of operations that span multiple method calls. You need to think about the atomicity of your business logic, not just the thread-safety of individual components.
内容的提问来源于stack exchange,提问作者Coder-Man

