java线程池在sa-token多端同步离线场景中,加锁核心是精准保护用户级共享状态临界区;应按userid粒度使用concurrenthashmap缓存reentrantlock,避免全局锁;优先利用redis原子操作替代jvm锁;异步任务中须严控锁内逻辑,读多写少时可用readwritelock分离。

Java线程池在基于Sa-Token权限框架实现“异步多端同步离线”时,加锁的核心目标不是锁住整个请求或用户会话,而是精准保护**共享状态变更的关键临界区**——比如用户登录态的多端同步标记、Token吊销缓存更新、设备列表一致性等。这类场景下,盲目用全局锁或方法级synchronized极易引发性能瓶颈甚至死锁,必须结合业务语义做细粒度、可重入、非阻塞友好的加锁设计。
按用户ID粒度加ReentrantLock(推荐)
多端同步离线操作本质是围绕“一个用户多个终端”的状态协同。应避免锁整个SaTokenManager或StpUtil,而为每个userId维护一个独立锁实例:
- 使用ConcurrentHashMap
缓存userId对应的锁对象,避免重复创建 - 加锁前先computeIfAbsent生成锁,再lock();务必在finally中unlock()
- 不直接锁String userId(因字符串常量池可能被复用),建议包装成new UserIdKey(userId)
- 示例:用户A在手机端执行“踢出其他端”,需原子更新其所有在线Token的isOnline字段并通知各端——该操作必须在userId锁内完成
用Sa-Token内置TokenStore做CAS式更新
Sa-Token默认使用RedisTokenStore时,天然支持原子操作。可绕过JVM锁,改用Redis的Lua脚本或INCR/SETNX保障一致性:
- 例如:调用StpRedisTemplate.set("satoken:logout:" + userId, "1", 30) 并检查返回值是否为true
- 对设备列表缓存(如"satoken:devices:" + userId),用HSET/HGETALL + WATCH/MULTI(Redis事务)替代本地锁
- 优势:跨JVM进程有效,适配分布式部署;劣势:需确保Redis高可用
异步任务中避免锁持有时间过长
线程池提交的异步任务(如推送离线消息、清理过期会话)若需加锁,必须严格控制锁内逻辑:
- 锁内只做状态标记(如设置isOffline = true)、写DB主键、发MQ事件
- 切勿在锁内调用远程HTTP、发邮件、等待IO——这些应解耦到锁外的后续异步链路(如CompletableFuture.thenRun)
- 考虑用tryLock(timeout, TimeUnit)代替lock(),超时则退避重试或降级为最终一致性处理
读多写少场景用ReadWriteLock分离
当“查询某用户当前在线设备数”远高于“执行踢出操作”时,可提升并发读性能:
- 用ReentrantReadWriteLock保护设备列表List
devices - getOnlineDeviceCount() → readLock().lock(),快速释放
- kickOtherDevices() → writeLock().lock(),独占更新
- 注意:writeLock会阻塞所有readLock,仍需控制写操作耗时
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











