
本文介绍如何让同一 JobModel 实例的 runSubJobs() 方法调用互斥执行,而不同实例间可并行运行,重点分析 synchronized(obj) 的原理、风险与更安全的替代方案(如专用锁对象和 java.util.concurrent 工具)。
本文介绍如何让同一 `jobmodel` 实例的 `runsubjobs()` 方法调用互斥执行,而不同实例间可并行运行,重点分析 `synchronized(obj)` 的原理、风险与更安全的替代方案(如专用锁对象和 `java.util.concurrent` 工具)。
在多线程环境下,若需保证对同一业务对象(如 JobModel)的操作串行化,而不同对象之间允许并发,最直观的思路是使用该对象本身作为同步监视器(monitor):
public void runSubJobs(JobModel jobModel) {
synchronized (jobModel) {
LOGGER.info("Start executing SubJobs for {}", jobModel.getId());
for (SubJobModel subJobModel : jobModel.getSubJobs()) {
run(subJobModel);
}
LOGGER.info("Finished executing SubJobs for {}", jobModel.getId());
}
}
✅ 该写法在技术上可行:synchronized(jobModel) 会尝试获取 jobModel 实例的内在锁(intrinsic lock)。由于锁绑定的是对象而非引用,只要多个线程传入的是同一个 JobModel 实例(即 == 相等),它们就会竞争同一把锁,从而实现“同对象串行、跨对象并行”的语义。
⚠️ 但存在严重设计风险:
-
jobModel是公共对象,其生命周期和使用范围不受你控制。其他模块可能无意中synchronized(jobModel)—— 例如用于状态同步或自定义序列化,这将导致意外的锁竞争甚至死锁,且难以排查; - 若
jobModel可被序列化(如作为 DTO 传输),直接用它加锁还可能引发反序列化后锁失效或安全问题; - 缺乏文档约束时,该锁行为属于“隐式契约”,违反了高内聚、低耦合的设计原则。
✅ 推荐实践:使用专用、私有锁对象
最佳做法是将锁内聚到 JobModel 内部,并严格限定访问权限:
public class JobModel implements Serializable {
private final Object subJobLock = new Object(); // 或 new Object[0](支持序列化)
// 其他字段...
private List<subjobmodel> subJobs;
// 提供受控的锁访问(仅包内可见)
Object getSubJobLock() {
return subJobLock;
}
// ... getter/setter ...
}</subjobmodel>
对应的服务类中使用该锁:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
public class SubJobExecutor {
public void runSubJobs(JobModel jobModel) {
// 安全:锁对象完全由 JobModel 封装,外部无法误用
synchronized (jobModel.getSubJobLock()) {
LOGGER.info("Start executing SubJobs for {}", jobModel.getId());
jobModel.getSubJobs().forEach(this::run);
LOGGER.info("Finished executing SubJobs for {}", jobModel.getId());
}
}
private void run(SubJobModel subJob) {
// 实际子任务执行逻辑
}
}
? 关键优势:锁对象
subJobLock是private final,仅通过包级方法暴露,杜绝了外部任意加锁的可能性,大幅提升可维护性与安全性。
? 进阶选择:使用 java.util.concurrent 工具类
对于更复杂的场景(如读多写少、需超时控制、异步解耦),建议迁移到 java.util.concurrent:
-
ReentrantLock:支持公平策略、可中断等待、超时获取等; -
StampedLock:适用于高读低写的场景,提供乐观读锁; -
ConcurrentHashMap+computeIfAbsent:实现细粒度、无锁化的按 Key 加锁(适合锁对象动态创建):
private final Map<jobmodel lock> lockMap = new ConcurrentHashMap();
public void runSubJobs(JobModel jobModel) {
Lock lock = lockMap.computeIfAbsent(jobModel, k -> new ReentrantLock());
lock.lock();
try {
LOGGER.info("Start executing SubJobs for {}", jobModel.getId());
jobModel.getSubJobs().forEach(this::run);
LOGGER.info("Finished executing SubJobs for {}", jobModel.getId());
} finally {
lock.unlock();
// 可选:避免内存泄漏,空闲后清理(需结合 WeakReference 或定时任务)
if (lock.getHoldCount() == 0) {
lockMap.remove(jobModel, lock);
}
}
}</jobmodel>
⚠️ 注意事项总结
- ❌ 避免
synchronized(null)—— 将抛出NullPointerException; - ❌ 禁止对可变对象(如
List、Map)或String等常量池对象加锁,易引发不可预知的竞争; - ✅ 锁对象应为
final,确保内存可见性与不可变性; - ✅ 所有
synchronized块或Lock.lock()必须配对finally中的解锁,防止死锁; - ? 并发逻辑无法通过单元测试充分验证——依赖 JVM 实现与运行时调度,务必优先选用经过充分验证的
java.util.concurrent组件。
综上,synchronized(jobModel) 虽能工作,但不是生产级推荐方案;使用封装良好的专用锁对象,或升级至 java.util.concurrent 工具集,才是保障线程安全、可维护性与可演进性的正确路径。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










