同一Java进程中DefaultLockRepository的TTL失效问题及解决方案咨询
问题本质
JdbcLockRegistry 内部维护了本地锁缓存(基于ConcurrentHashMap),同一Java进程内的线程会优先读取本地缓存的锁状态,而非直接查询数据库的TTL记录。当某个线程持锁挂起时,本地缓存会一直标记该锁为“已持有”,即使数据库中的锁记录已过期,后续线程也无法触发TTL校验逻辑,导致TTL机制失效。
解决方案
针对同一进程内的锁TTL失效问题,可通过以下几种方式解决:
1. 禁用本地锁缓存
绕过JdbcLockRegistry的本地缓存逻辑,强制所有锁操作直接与数据库交互,确保每次获取锁时都校验数据库中的TTL状态。
- 核心实现示例:
public class NoCacheJdbcLockRegistry implements LockRegistry { private final DefaultLockRepository lockRepository; public NoCacheJdbcLockRegistry(DefaultLockRepository lockRepository) { this.lockRepository = lockRepository; } @Override public Lock obtainLock(Object lockKey) { String key = lockKey.toString(); return new JdbcLock(key, lockRepository); } private static class JdbcLock implements Lock { private final String key; private final DefaultLockRepository repository; public JdbcLock(String key, DefaultLockRepository repository) { this.key = key; this.repository = repository; } @Override public void lock() { while (!repository.acquire(key)) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException("Lock acquisition interrupted"); } } } @Override public void unlock() { repository.release(key); } // 实现Lock接口的其他方法(lockInterruptibly、tryLock等),均直接调用repository对应逻辑 } }
2. 定时清理本地过期锁
给JdbcLockRegistry添加定时任务,定期对比本地缓存锁与数据库TTL记录,清理已过期的本地锁标记。
- 核心实现示例:
public class TtlAwareJdbcLockRegistry extends JdbcLockRegistry { private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final long ttlMillis; private final ConcurrentMap<Object, Lock> locks; @SuppressWarnings("unchecked") public TtlAwareJdbcLockRegistry(DataSource dataSource, String registryKey, long ttlMillis) { super(dataSource, registryKey); this.ttlMillis = ttlMillis; // 通过反射获取父类的locks缓存(或自定义时直接暴露该字段) try { Field locksField = JdbcLockRegistry.class.getDeclaredField("locks"); locksField.setAccessible(true); this.locks = (ConcurrentMap<Object, Lock>) locksField.get(this); } catch (Exception e) { throw new RuntimeException("Failed to access locks cache", e); } // 每5秒执行一次过期锁清理 scheduler.scheduleAtFixedRate(this::cleanExpiredLocks, 5, 5, TimeUnit.SECONDS); } private void cleanExpiredLocks() { long currentTime = System.currentTimeMillis(); locks.keySet().forEach(key -> { String lockKey = key.toString(); LockRecord record = getLockRepository().findById(lockKey).orElse(null); if (record != null && currentTime - record.getLastUpdated().getTime() > ttlMillis) { locks.remove(key); } }); } public void shutdown() { scheduler.shutdown(); } }
3. 替换为进程感知的分布式锁实现
改用支持同一进程内TTL校验的锁组件,避免本地缓存带来的问题:
- 使用Redisson锁:基于Redis的分布式锁,自带看门狗机制,同一进程内的线程会自动感知锁的过期状态,无需额外处理本地缓存。
- 实现双重锁校验:每次获取锁时,先尝试本地锁,失败则查询数据库锁状态,若数据库锁已过期则强制清理本地锁后重试。
临时应急方案
如果无法立即修改代码,可通过以下方式临时缓解:
- 给持锁线程添加超时中断:在调用第三方Jar包的逻辑外层设置超时,超时后中断线程,触发锁释放。
- 缩短TTL配置:减小锁的TTL时间,降低锁被持有的阻塞窗口。
内容的提问来源于stack exchange,提问作者Cui Pengfei 崔鹏飞
相关产品推荐
相关产品推荐

