如何基于条件阻塞线程?多线程调用方法场景技术咨询
Alright, let's tackle this thread blocking problem for your notifyCompleted method. From the code snippet you shared, I assume the core need is to handle concurrent calls safely—probably blocking threads when they try to modify the same player's ranked data, or waiting for specific business conditions to be met before proceeding. Let's break down practical, actionable solutions:
If your primary goal is to ensure serial execution of operations for the same player (avoiding concurrent modifications to PlayerRankedData), using a per-player lock is the way to go. This lets threads handling different players run in parallel, while blocking threads that target the same player.
Here's how to implement it:
// Class-level map to hold locks for each player (thread-safe via ConcurrentHashMap) private final ConcurrentHashMap<Long, ReentrantLock> playerLocks = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Long, PlayerRankedData> rankedDataMap; // Assume this is thread-safe private final int minimumMatchPoints; public void notifyCompleted(PlayerDTO winner, PlayerDTO loser) { // Get or create a lock for each player atomically ReentrantLock winnerLock = playerLocks.computeIfAbsent(winner.getId(), k -> new ReentrantLock()); ReentrantLock loserLock = playerLocks.computeIfAbsent(loser.getId(), k -> new ReentrantLock()); // Critical: Lock in a fixed order to avoid deadlocks! long winnerId = winner.getId(); long loserId = loser.getId(); ReentrantLock firstLock = winnerId < loserId ? winnerLock : loserLock; ReentrantLock secondLock = winnerId < loserId ? loserLock : winnerLock; try { firstLock.lock(); secondLock.lock(); // Your original business logic, now safe from concurrent modifications PlayerRankedData winnerData = rankedDataMap.get(winner.getId()); PlayerRankedData loserData = rankedDataMap.get(loser.getId()); int pointsToAdd = getPointsToAdd(winnerData, loserData); int pointsToDeduct = getPointsToDeduct(loserData, winnerData); winnerData.addMatchPoints(pointsToAdd); loserData.deductMatchPointsUptoMinimum(pointsToDeduct, minimumMatchPoints); winnerData.addRewards(/* Your reward logic here */); // ... rest of your method } finally { // Always release locks in the finally block to avoid leaks secondLock.unlock(); firstLock.unlock(); } }
Key Notes:
computeIfAbsentensures we create a lock for a player only once, atomically.- Locking by player ID order (smaller ID first) eliminates deadlocks that could happen if two threads try to lock players A+B and B+A at the same time.
- This maintains good concurrency: threads handling unrelated players won't block each other.
Condition (For Custom Business Rules) If you need to block threads until a specific business condition is met (e.g., the player isn't in an active match, or their data is in a valid state), combine ReentrantLock with Condition to wait and wake threads as needed.
Example implementation for waiting until a player is not in a match:
private final ConcurrentHashMap<Long, PlayerLockCondition> playerLockConditions = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Long, PlayerRankedData> rankedDataMap; private final int minimumMatchPoints; // Helper class to wrap lock and condition per player private static class PlayerLockCondition { final ReentrantLock lock = new ReentrantLock(); final Condition playerReady = lock.newCondition(); } public void notifyCompleted(PlayerDTO winner, PlayerDTO loser) throws InterruptedException { PlayerLockCondition winnerLockCond = playerLockConditions.computeIfAbsent(winner.getId(), k -> new PlayerLockCondition()); PlayerLockCondition loserLockCond = playerLockConditions.computeIfAbsent(loser.getId(), k -> new PlayerLockCondition()); // Again, lock in fixed order to prevent deadlocks long winnerId = winner.getId(); long loserId = loser.getId(); PlayerLockCondition firstLockCond = winnerId < loserId ? winnerLockCond : loserLockCond; PlayerLockCondition secondLockCond = winnerId < loserId ? loserLockCond : winnerLockCond; try { firstLockCond.lock.lock(); secondLockCond.lock.lock(); PlayerRankedData winnerData = rankedDataMap.get(winner.getId()); PlayerRankedData loserData = rankedDataMap.get(loser.getId()); // Wait until the player is ready (e.g., not in a match) while (winnerData.isInMatch()) { winnerLockCond.playerReady.await(); // Blocks until signaled } while (loserData.isInMatch()) { loserLockCond.playerReady.await(); } // Execute your business logic once conditions are met int pointsToAdd = getPointsToAdd(winnerData, loserData); int pointsToDeduct = getPointsToDeduct(loserData, winnerData); winnerData.addMatchPoints(pointsToAdd); loserData.deductMatchPointsUptoMinimum(pointsToDeduct, minimumMatchPoints); winnerData.addRewards(/* Your reward logic here */); // ... rest of your method // Notify waiting threads that the player's state has changed winnerLockCond.playerReady.signalAll(); loserLockCond.playerReady.signalAll(); } finally { secondLockCond.lock.unlock(); firstLockCond.lock.unlock(); } } // Example method to check player readiness private boolean isPlayerReady(PlayerRankedData data) { return !data.isInMatch(); }
Key Notes:
- Use a
whileloop to re-check conditions after waking up—threads can be woken spuriously without the condition being met. signalAll()wakes all threads waiting on the condition, which is safer thansignal()(which only wakes one thread) if multiple threads might be waiting.- This is flexible enough to adapt to any custom condition you need (e.g., waiting for a player's cooldown to expire).
- Ensure
rankedDataMapis thread-safe (useConcurrentHashMapinstead of a regularHashMap). - Clean up unused locks: If players can be removed from the system, remove their lock entries from the map to avoid memory leaks.
- Avoid global locks—they kill concurrency. Stick to fine-grained per-player locks whenever possible.
内容的提问来源于stack exchange,提问作者Sneh

