必须使用类锁(synchronized(shardrouter.class)),因其作用于唯一class对象,能确保全局静态路由元数据更新的串行化,避免多实例调用导致的锁失效、路由错乱或复合操作不一致。

在数据网关(如基于ShardingSphere或自研分片路由中间件)中,当需要动态切换Shard分片路由表(例如从 t_order_2023 切到 t_order_2024),且该切换涉及全局共享的路由元数据(如 RouteTableMap、ShardRuleHolder 等单例类的状态更新)时,synchronized 用于保证**类级别临界区的线程安全**——即确保同一时刻只有一个线程能执行路由表刷新逻辑,防止多线程并发修改导致路由错乱、脏读或 NPE。
为什么必须用类锁(synchronized(XXX.class))而非对象锁?
分片路由表切换通常是全局行为,影响所有后续 SQL 路由决策。相关状态通常存于静态字段或单例 Bean 中(如 static Map<string list>> tableRoutes</string>)。若用实例锁(synchronized(this)),而调用方持有多个不同实例(如 Spring 多个 @Service bean 或不同线程创建的临时对象),锁将失效。类锁作用于 Class 对象本身,天然唯一,能覆盖所有实例和静态上下文。
- 静态路由缓存更新(如
ShardTableRouter.updateRoutingTable())必须串行化 - 避免 A 线程刚加载新表名,B 线程仍按旧映射路由,造成写入错库/错表
- 类锁不依赖对象生命周期,适合在 Spring 容器启动后长期运行的网关场景
典型使用方式:静态方法 + 类锁
推荐将切换逻辑封装为工具类的静态方法,并显式使用 ShardRouter.class 作为锁对象,清晰表达“这是对整个路由系统的排他操作”:
public class ShardRouter {
private static volatile Map<string list>> routingTable = new ConcurrentHashMap();
public static void switchTable(String logicTable, List<string> newActualTables) {
synchronized (ShardRouter.class) {
// 1. 清理旧路由(可选)
routingTable.remove(logicTable);
// 2. 写入新路由(注意:ConcurrentHashMap 仅保证单操作线程安全,复合操作仍需同步)
routingTable.put(logicTable, new ArrayList(newActualTables));
// 3. 触发监听器、刷新本地缓存、广播事件等
notifyRouteChanged(logicTable);
}
}
}</string></string>
⚠️ 注意:ConcurrentHashMap 不能替代此处的同步块——因为 remove + put 是非原子的复合操作,且可能需额外业务逻辑(如日志、事件通知),必须整体受控。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
与 Spring 场景结合的注意事项
若路由管理由 Spring 管理(如 @Service 单例),仍建议用类锁而非 this,理由如下:
- Spring 单例 Bean 的
this锁只对当前 Bean 实例有效;但网关可能有多个注入点(如 Filter、Interceptor、Controller 各自调用),若未严格统一调用入口,易漏锁 - 某些场景下(如单元测试、AOP 代理)
this可能指向代理对象或原始对象,语义模糊 - 类锁明确表达“系统级变更”,便于代码审查与监控(如通过 JVM TI 检测
ShardRouter.class的锁竞争)
替代方案对比:ReentrantLock vs synchronized
在简单切换场景中,synchronized(ShardRouter.class) 更轻量、无须手动释放、JVM 优化成熟(偏向锁 → 轻量级锁 → 重量级锁自适应)。除非需要:
- 尝试获取锁失败立即返回(
tryLock()) - 可中断等待(
lockInterruptibly()) - 多条件等待(
Condition)
否则无需升级为 ReentrantLock。过度设计反而增加维护成本和死锁风险。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










