Java同步块动态锁监控问题:同哈希线程等待异哈希线程放行
问题分析与解决方案
看起来你踩了一个经典的锁对象不唯一的坑!问题根源有两个:
- 你用
synchronized(campaignEnity.getHash()),但getHash()每次返回新的String对象——虽然字符串内容相同,但对象本身是独立的,所以每个线程其实都在锁自己的专属对象,完全起不到同哈希值线程间的同步作用。 - 你尝试用静态Map存锁,但大概率是没用线程安全的Map实现,或者没处理并发创建锁的原子性逻辑,导致相同哈希值的线程拿到了不同的锁对象,自然没法互相等待。
可靠解决方案:用ConcurrentHashMap管理锁对象
我们可以用ConcurrentHashMap(线程安全的哈希表)来存储哈希值对应的锁对象,通过它的computeIfAbsent方法原子性地获取或创建锁,保证相同哈希值的线程一定会拿到同一个锁对象。
代码示例
import java.util.concurrent.ConcurrentHashMap; public class YourCampaignProcessor { // 静态线程安全Map,存储哈希值 -> 对应锁对象 private static final ConcurrentHashMap<String, Object> HASH_LOCK_MAP = new ConcurrentHashMap<>(); public CampaignProcessorEntity processCampaign(CampaignProcessorEntity campaignEntity) { String targetHash = campaignEntity.getHash(); // 原子操作:如果哈希值对应的锁不存在,就新建一个Object作为锁;否则直接返回已有的锁 Object lock = HASH_LOCK_MAP.computeIfAbsent(targetHash, k -> new Object()); synchronized (lock) { try { // 这里放你需要运行数分钟的业务代码 // ... } finally { // 【可选】如果该哈希值不会再被后续线程使用,可以移除锁对象避免内存泄漏 // 注意:如果哈希值会重复出现,不要移除!否则新线程会创建新锁,和旧线程的锁不同步 // HASH_LOCK_MAP.remove(targetHash); } } return campaignEntity; } }
为什么这个方案能解决问题?
ConcurrentHashMap本身是线程安全的,computeIfAbsent方法是原子执行的——多个线程同时请求同一个哈希值的锁时,只会有一个线程创建锁对象,其他线程直接复用已创建的锁,完美保证锁的唯一性。- 我们用独立的
Object作为锁对象,避开了String常量池可能带来的意外锁共享问题(比如其他地方也用相同内容的String当锁,会导致不必要的同步)。
额外注意事项:内存泄漏问题
如果你的哈希值数量非常多,而且很多哈希值只会被使用一次,HASH_LOCK_MAP会一直持有锁对象导致内存占用持续增加。这种情况可以考虑用带过期策略的缓存来管理锁,比如Guava的LoadingCache:
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.concurrent.TimeUnit; public class YourCampaignProcessor { // 缓存锁对象,10分钟未访问自动过期移除 private static final LoadingCache<String, Object> LOCK_CACHE = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) .build(new CacheLoader<String, Object>() { @Override public Object load(String hash) { return new Object(); } }); public CampaignProcessorEntity processCampaign(CampaignProcessorEntity campaignEntity) { String targetHash = campaignEntity.getHash(); Object lock = LOCK_CACHE.getUnchecked(targetHash); synchronized (lock) { // 业务代码 // ... } return campaignEntity; } }
这个方案会自动清理长时间未使用的锁对象,避免内存泄漏,但需要引入Guava库。如果不想引入第三方库,也可以定期手动清理Map里的过期锁对象,但实现起来会麻烦一些。
内容的提问来源于stack exchange,提问作者Don Jose
相关产品推荐
相关产品推荐

