Semaphore场景下多线程访问同一数据结构是否需额外锁机制?
Great question—let’s unpack this step by step because it’s a common point of confusion between concurrency control tools.
First, let’s clarify what a Semaphore actually does: it’s a concurrency limiter. When you set it to allow 10 threads into your function, it only ensures that no more than 10 threads are executing that function at the same time. It does nothing to coordinate how those 10 threads interact with shared data structures inside the function.
What happens if 10 threads hit the same data structure simultaneously?
You’ll run into classic race conditions and data inconsistency issues. For example:
- If you’re using a non-thread-safe collection like
ArrayList, one thread might be in the middle of adding an element (resizing the underlying array) while another tries to read its size—leading to index out-of-bounds errors or missing elements. - If multiple threads are incrementing a shared counter without synchronization, you could end up with a final count that’s lower than expected (since two threads might read the same value, increment it, and write back the same updated number).
- For more complex data structures, you might see corrupted state, partial updates, or unexpected behavior that’s hard to reproduce and debug.
Do you need additional locking?
Absolutely. Here’s why:
Semaphores control the number of concurrent threads, but they don’t enforce mutual exclusion for shared resources. To safely access the data structure, you need a mechanism that ensures only one thread can modify (or read-modify-write) the data at a time.
Your options include:
- Using
synchronizedblocks/methods around the code that accesses the shared data. - Using explicit locks like
ReentrantLockfor more flexibility (e.g., timed waits, interruptible locks). - Swapping to a thread-safe data structure (like
ConcurrentHashMap,CopyOnWriteArrayList, orAtomicInteger) that handles synchronization internally.
Here’s a quick example to illustrate the difference:
// Semaphore limits to 10 threads, but no lock on shared list Semaphore semaphore = new Semaphore(10); List<String> nonThreadSafeList = new ArrayList<>(); public void addToList(String item) throws InterruptedException { semaphore.acquire(); // Risky! Multiple threads can hit this line at the same time nonThreadSafeList.add(item); semaphore.release(); } // Fixed version with ReentrantLock ReentrantLock lock = new ReentrantLock(); public void safeAddToList(String item) throws InterruptedException { semaphore.acquire(); lock.lock(); try { nonThreadSafeList.add(item); } finally { lock.unlock(); semaphore.release(); } }
In the first version, even with the semaphore, 10 threads can still trample over each other’s changes to the ArrayList. The second version adds a lock to ensure only one thread modifies the list at a time, while still limiting total concurrent threads to 10.
Key Takeaway
Think of semaphores as a "bouncer" that lets a fixed number of people into a room. But once they’re inside, you still need rules (locks) to make sure they don’t all grab the same plate of food at the same time. The two tools solve different problems—they’re complementary, not interchangeable.
内容的提问来源于stack exchange,提问作者Jana

