多线程安全类中分组锁同步add方法与全局clear方法的互斥实现咨询
多线程安全类中分组锁同步add方法与全局clear方法的互斥实现咨询
看起来你已经找对了分组同步add方法的核心方向,但卡在了clear方法和所有add操作的全局互斥上——这个需求确实有点绕,不过咱们可以通过分层锁+读写锁的思路来解决,同时还能保证两个ConcurrentMap修改的原子性。
先说说你现有方案的问题:你用ConcurrentHashMap存分组锁的方式,能很好地实现“相同param1+param2组合的add互斥、不同组合并行”的需求,但clear方法没法直接锁定所有分组锁——这些锁是动态生成的,而且同时锁定多个对象锁本身就容易引发死锁,根本不现实。
接下来给你一个具体的实现方案:
我们可以引入一个全局的ReentrantReadWriteLock,把add操作归为“共享读操作”(允许多个不同分组的add并行执行),clear操作归为“独占写操作”(必须等所有add都执行完才能启动),再结合你原来的分组锁,就能完美满足所有需求:
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ThreadSafeClass { private final ConcurrentMap<String, String> map1 = new ConcurrentHashMap<>(); private final ConcurrentMap<String, String> map2 = new ConcurrentHashMap<>(); private final ConcurrentMap<String, Object> groupLocks = new ConcurrentHashMap<>(); private final ReentrantReadWriteLock globalLock = new ReentrantReadWriteLock(); private final ReentrantReadWriteLock.ReadLock readLock = globalLock.readLock(); private final ReentrantReadWriteLock.WriteLock writeLock = globalLock.writeLock(); public void add(String param1, String param2) { // 先拿全局读锁:确保clear没办法在当前add执行期间启动 readLock.lock(); try { // 获取对应分组的锁:保证相同param组合的add不会同时执行 String lockKey = param1 + ":" + param2; Object lock = groupLocks.computeIfAbsent(lockKey, k -> new Object()); synchronized (lock) { // 这里修改两个map是安全的:同一分组的add被互斥,且clear无法进入 map1.putIfAbsent(param1, param2); map2.putIfAbsent(param2, param1); // 替换成你的实际业务逻辑即可 } } finally { // 一定要在finally里释放锁,避免异常导致锁泄漏 readLock.unlock(); } } public void clear() { // 拿全局写锁:此时所有新的add都没法获取读锁,已经在跑的add会执行完 writeLock.lock(); try { // 清空两个map:此时没有任何add在执行,绝对不会出现“清空一半”的情况 map1.clear(); map2.clear(); // 可选:清理分组锁,避免长期不用的锁占用内存 groupLocks.clear(); } finally { writeLock.unlock(); } } }
这个方案的优势很明显:
- 完全满足add的分组互斥需求:相同param组合的add会被同一个分组锁阻塞,不同组合的add因为持有共享的读锁,可以并行跑,不影响性能。
- 完美解决clear和add的互斥问题:clear拿到写锁后,所有新的add都得等clear做完才能开始;已经在执行的add会正常完成,不会被中途打断,彻底避免了你担心的“clear清空一半map,add刚完成一个put”的问题。
- 保证两个map修改的原子性:在分组锁和读锁的双重保护下,同一分组的add不会并发修改,clear也不会在add修改期间捣乱,所以两个putIfAbsent操作是原子的。
最后给你两个优化建议:
- 内存泄漏问题:如果你的param组合会无限增长,groupLocks会一直存着所有用过的锁对象。这种情况可以考虑用Guava的
Cache替代ConcurrentHashMap,设置过期时间自动清理闲置的锁;或者额外维护一个ConcurrentMap<String, AtomicInteger>记录每个锁的使用次数,当add释放锁时计数减1,减到0就从groupLocks里删掉这个锁。 - 性能优化:如果对性能要求极高,可以用
StampedLock替代ReentrantReadWriteLock,它的读写性能更好,但API稍微复杂一点,普通场景下ReentrantReadWriteLock完全够用。
内容来源于stack exchange
相关产品推荐
相关产品推荐

